@ketecode/loom 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -11
- package/agents/code-reviewer/evals/behavior.json +60 -0
- package/agents/inbox-triager/AGENT.md +98 -0
- package/agents/inbox-triager/evals/triggers.json +16 -0
- package/agents/spec-reviewer/AGENT.md +95 -0
- package/agents/spec-reviewer/evals/behavior.json +57 -0
- package/agents/spec-reviewer/evals/triggers.json +16 -0
- package/agents/task-implementer/AGENT.md +89 -0
- package/agents/task-implementer/evals/behavior.json +47 -0
- package/agents/task-implementer/evals/triggers.json +16 -0
- package/bin/kete-loom.js +46 -6
- package/commands/brainstorm.md +25 -0
- package/commands/debug.md +27 -0
- package/commands/dual-review.md +34 -0
- package/commands/feature-dev.md +8 -7
- package/commands/finish-branch.md +26 -0
- package/commands/fix-bug.md +38 -0
- package/commands/production-readiness.md +45 -0
- package/commands/usage-report.md +43 -0
- package/hooks/block-foreground-servers/HOOK.md +34 -0
- package/hooks/block-foreground-servers/cases.json +31 -0
- package/hooks/block-foreground-servers/hook.js +41 -0
- package/hooks/capture-lessons/HOOK.md +24 -0
- package/hooks/capture-lessons/hook.js +48 -0
- package/hooks/notify-on-stop/HOOK.md +25 -0
- package/hooks/notify-on-stop/hook.js +27 -0
- package/hooks/protect-lint-config/HOOK.md +29 -0
- package/hooks/protect-lint-config/cases.json +18 -0
- package/hooks/protect-lint-config/fixtures/.eslintrc.json +1 -0
- package/hooks/protect-lint-config/fixtures/.golangci.yml +1 -0
- package/hooks/protect-lint-config/fixtures/.prettierrc +1 -0
- package/hooks/protect-lint-config/fixtures/.rubocop.yml +1 -0
- package/hooks/protect-lint-config/fixtures/biome.json +1 -0
- package/hooks/protect-lint-config/fixtures/eslint.config.mjs +1 -0
- package/hooks/protect-lint-config/fixtures/ruff.toml +1 -0
- package/hooks/protect-lint-config/hook.js +25 -0
- package/hooks/read-before-edit/HOOK.md +26 -0
- package/hooks/read-before-edit/cases.json +15 -0
- package/hooks/read-before-edit/hook.js +50 -0
- package/hooks/run.js +22 -6
- package/hooks/verify-before-stop/HOOK.md +32 -0
- package/hooks/verify-before-stop/hook.js +70 -0
- package/mcp/clickhouse.json +11 -0
- package/mcp/exa.json +11 -0
- package/mcp/firecrawl.json +11 -0
- package/mcp/vercel.json +11 -0
- package/package.json +6 -4
- package/profiles.json +54 -5
- package/rules/use-skills.md +23 -0
- package/scripts/lib/behavior.js +52 -0
- package/scripts/lib/hooks-install.js +9 -7
- package/scripts/lib/lint.js +24 -2
- package/scripts/lib/mcp-install.js +5 -1
- package/scripts/lib/render.js +6 -0
- package/scripts/lib/skills.js +1 -0
- package/scripts/lib/targets.js +10 -0
- package/skills/agent-failure-debugging/SKILL.md +92 -0
- package/skills/agent-failure-debugging/evals/triggers.json +16 -0
- package/skills/agent-outbound-messaging/SKILL.md +92 -0
- package/skills/agent-outbound-messaging/evals/triggers.json +16 -0
- package/skills/agent-security/SKILL.md +117 -0
- package/skills/agent-security/evals/triggers.json +16 -0
- package/skills/agent-security/references/host-configs.md +37 -0
- package/skills/api-client-integration/SKILL.md +86 -0
- package/skills/api-client-integration/evals/triggers.json +16 -0
- package/skills/api-client-integration/references/resilience.md +57 -0
- package/skills/blender-automation/SKILL.md +100 -0
- package/skills/blender-automation/evals/triggers.json +16 -0
- package/skills/blender-automation/references/batch-and-render.md +77 -0
- package/skills/blender-automation/references/bpy-inspection.md +91 -0
- package/skills/blender-automation/references/rig-and-retarget-checks.md +23 -0
- package/skills/brand-strategy/SKILL.md +83 -0
- package/skills/brand-strategy/evals/triggers.json +16 -0
- package/skills/change-impact-analysis/SKILL.md +89 -0
- package/skills/change-impact-analysis/evals/triggers.json +16 -0
- package/skills/change-impact-analysis/references/graph-commands.md +28 -0
- package/skills/contract-drafting/SKILL.md +96 -0
- package/skills/contract-drafting/evals/triggers.json +16 -0
- package/skills/contract-drafting/references/term-sheets.md +68 -0
- package/skills/contract-first-development/SKILL.md +86 -0
- package/skills/contract-first-development/evals/triggers.json +16 -0
- package/skills/contract-first-development/references/tooling.md +46 -0
- package/skills/decision-council/SKILL.md +98 -0
- package/skills/decision-council/evals/triggers.json +16 -0
- package/skills/design-brainstorming/SKILL.md +132 -0
- package/skills/design-brainstorming/evals/behavior.json +55 -0
- package/skills/design-brainstorming/evals/triggers.json +16 -0
- package/skills/design-brainstorming/references/visual-mockups.md +34 -0
- package/skills/desktop-app-testing/SKILL.md +104 -0
- package/skills/desktop-app-testing/evals/triggers.json +16 -0
- package/skills/desktop-app-testing/references/drivers.md +81 -0
- package/skills/document-processing/SKILL.md +100 -0
- package/skills/document-processing/evals/triggers.json +17 -0
- package/skills/document-processing/references/extraction-and-ocr.md +71 -0
- package/skills/document-processing/references/forms-and-conversion.md +69 -0
- package/skills/document-processing/references/redaction.md +61 -0
- package/skills/energy-procurement/SKILL.md +106 -0
- package/skills/energy-procurement/evals/triggers.json +16 -0
- package/skills/finishing-a-branch/SKILL.md +127 -0
- package/skills/finishing-a-branch/evals/behavior.json +49 -0
- package/skills/finishing-a-branch/evals/triggers.json +16 -0
- package/skills/freight-carrier-management/SKILL.md +107 -0
- package/skills/freight-carrier-management/evals/triggers.json +16 -0
- package/skills/git-worktrees/SKILL.md +130 -0
- package/skills/git-worktrees/evals/behavior.json +56 -0
- package/skills/git-worktrees/evals/triggers.json +16 -0
- package/skills/google-workspace-integration/SKILL.md +65 -0
- package/skills/google-workspace-integration/evals/triggers.json +17 -0
- package/skills/google-workspace-integration/references/api.md +121 -0
- package/skills/harmonyos-arkts/SKILL.md +117 -0
- package/skills/harmonyos-arkts/evals/triggers.json +16 -0
- package/skills/harmonyos-arkts/references/build-errors.md +42 -0
- package/skills/implementation-plans/SKILL.md +117 -0
- package/skills/implementation-plans/evals/behavior.json +54 -0
- package/skills/implementation-plans/evals/triggers.json +16 -0
- package/skills/ios-platform-features/SKILL.md +99 -0
- package/skills/ios-platform-features/evals/triggers.json +16 -0
- package/skills/ios-platform-features/references/foundation-models.md +90 -0
- package/skills/jvm-sql-mappers/SKILL.md +98 -0
- package/skills/jvm-sql-mappers/evals/triggers.json +16 -0
- package/skills/jvm-sql-mappers/references/examples.md +129 -0
- package/skills/mobile-accessibility/SKILL.md +94 -0
- package/skills/mobile-accessibility/evals/triggers.json +16 -0
- package/skills/network-design/SKILL.md +98 -0
- package/skills/network-design/evals/triggers.json +16 -0
- package/skills/observability/SKILL.md +125 -0
- package/skills/observability/evals/triggers.json +17 -0
- package/skills/observability/references/otel-setup.md +87 -0
- package/skills/observability/references/slo-alerts.md +70 -0
- package/skills/open-source-release/SKILL.md +107 -0
- package/skills/open-source-release/evals/triggers.json +15 -0
- package/skills/open-source-release/references/history-scrub.md +65 -0
- package/skills/patent-research/SKILL.md +94 -0
- package/skills/patent-research/evals/triggers.json +16 -0
- package/skills/patent-research/references/databases-and-queries.md +52 -0
- package/skills/prospect-research/SKILL.md +87 -0
- package/skills/prospect-research/evals/triggers.json +16 -0
- package/skills/python-app-distribution/SKILL.md +107 -0
- package/skills/python-app-distribution/evals/triggers.json +16 -0
- package/skills/python-app-distribution/references/pypi-trusted-publishing.md +55 -0
- package/skills/python-app-distribution/references/windows-macos-signing.md +70 -0
- package/skills/quality-nonconformance/SKILL.md +107 -0
- package/skills/quality-nonconformance/evals/triggers.json +16 -0
- package/skills/quality-nonconformance/references/8d-and-capa.md +46 -0
- package/skills/react-testing/SKILL.md +121 -0
- package/skills/react-testing/evals/triggers.json +18 -0
- package/skills/react-testing/references/msw.md +64 -0
- package/skills/react-testing/references/queries.md +41 -0
- package/skills/react-testing/references/setup.md +74 -0
- package/skills/recommender-systems/SKILL.md +119 -0
- package/skills/recommender-systems/evals/triggers.json +16 -0
- package/skills/recommender-systems/references/evaluation.md +39 -0
- package/skills/refactoring-safely/evals/behavior.json +57 -0
- package/skills/reproducible-dev-environments/SKILL.md +132 -0
- package/skills/reproducible-dev-environments/evals/triggers.json +16 -0
- package/skills/responding-to-code-review/SKILL.md +124 -0
- package/skills/responding-to-code-review/evals/behavior.json +54 -0
- package/skills/responding-to-code-review/evals/triggers.json +16 -0
- package/skills/security-review/evals/behavior.json +56 -0
- package/skills/session-handoff/evals/behavior.json +55 -0
- package/skills/skill-authoring/SKILL.md +13 -2
- package/skills/skill-authoring/evals/triggers.json +2 -1
- package/skills/skill-authoring/references/pressure-testing.md +87 -0
- package/skills/slide-decks/SKILL.md +107 -0
- package/skills/slide-decks/evals/triggers.json +18 -0
- package/skills/slide-decks/references/html-decks.md +86 -0
- package/skills/slide-decks/references/layouts.md +45 -0
- package/skills/slide-decks/references/pptx.md +87 -0
- package/skills/subagent-driven-development/SKILL.md +109 -0
- package/skills/subagent-driven-development/evals/behavior.json +53 -0
- package/skills/subagent-driven-development/evals/triggers.json +15 -0
- package/skills/subagent-driven-development/references/implementer-brief.md +60 -0
- package/skills/subagent-driven-development/references/reviewer-briefs.md +52 -0
- package/skills/systematic-debugging/SKILL.md +92 -0
- package/skills/systematic-debugging/evals/behavior.json +59 -0
- package/skills/systematic-debugging/evals/triggers.json +16 -0
- package/skills/systematic-debugging/references/condition-waiting.md +52 -0
- package/skills/systematic-debugging/references/root-cause-tracing.md +49 -0
- package/skills/systematic-debugging/references/test-pollution.md +46 -0
- package/skills/tdd-workflow/SKILL.md +3 -1
- package/skills/tdd-workflow/evals/behavior.json +55 -0
- package/skills/tdd-workflow/evals/triggers.json +2 -1
- package/skills/tdd-workflow/references/testing-anti-patterns.md +87 -0
- package/skills/transactional-email/SKILL.md +68 -0
- package/skills/transactional-email/evals/triggers.json +17 -0
- package/skills/transactional-email/references/providers.md +63 -0
- package/skills/ui-demo-recording/SKILL.md +93 -0
- package/skills/ui-demo-recording/evals/triggers.json +16 -0
- package/skills/ui-demo-recording/references/cursor-and-captions.md +48 -0
- package/skills/ui-demo-recording/references/playwright-recording.md +63 -0
- package/skills/ui-demo-recording/references/post-processing.md +57 -0
- package/skills/ui-design-polish/SKILL.md +113 -0
- package/skills/ui-design-polish/evals/triggers.json +18 -0
- package/skills/ui-design-polish/references/detail-checklist.md +45 -0
- package/skills/ui-design-polish/references/tokens.md +68 -0
- package/skills/ui-flow-audit/SKILL.md +112 -0
- package/skills/ui-flow-audit/evals/triggers.json +17 -0
- package/skills/ui-motion/SKILL.md +103 -0
- package/skills/ui-motion/evals/triggers.json +18 -0
- package/skills/ui-motion/references/patterns.md +138 -0
- package/skills/ui-motion/references/tokens.md +52 -0
- package/skills/unicode-text-processing/SKILL.md +103 -0
- package/skills/unicode-text-processing/evals/triggers.json +17 -0
- package/skills/unicode-text-processing/references/apis.md +89 -0
- package/skills/unicode-text-processing/references/script-notes.md +50 -0
- package/skills/verification-loop/evals/behavior.json +52 -0
- package/skills/video-editing/SKILL.md +111 -0
- package/skills/video-editing/evals/triggers.json +16 -0
- package/skills/video-editing/references/edl-and-logging.md +44 -0
- package/skills/video-editing/references/ffmpeg-edit-recipes.md +92 -0
- package/skills/video-editing/references/resolve-scripting.md +53 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Provider notes for transactional email
|
|
2
|
+
|
|
3
|
+
Read the section for the provider in use. Confirm field names against the provider's current API reference before shipping.
|
|
4
|
+
|
|
5
|
+
## Postmark
|
|
6
|
+
|
|
7
|
+
- Send: `POST https://api.postmarkapp.com/email` (`From`, `To`, `Subject`, `HtmlBody`, `TextBody`, `MessageStream`, `Tag`, `Metadata`). Templates: `POST /email/withTemplate` with `TemplateAlias` or `TemplateId` and `TemplateModel`; batch with templates: `POST /email/batchWithTemplates`, up to 500 messages, returns 200 even if some fail, so check each result's `ErrorCode`.
|
|
8
|
+
- Auth header: `X-Postmark-Server-Token`. Test token: send `POSTMARK_API_TEST` as the server token to validate requests without delivering.
|
|
9
|
+
- Streams: `outbound` is the default transactional stream; create a broadcast stream for bulk mail. Omitting `MessageStream` uses `outbound`.
|
|
10
|
+
- Idempotency: no idempotency key in the API. Rely on your outbox unique key.
|
|
11
|
+
- Webhooks: Delivery, Bounce, Spam complaint, Open, Click, Subscription change. Postmark does **not** sign webhooks: protect the URL with HTTP basic auth credentials in the URL, HTTPS, and an allow-list of Postmark's published webhook IPs. Retries 5xx, 408, 429 and network failures (1, 5, 10, 10, 10, 15 minutes); other 4xx are dropped, so return 2xx once stored.
|
|
12
|
+
- Suppressions are per stream; hard bounces and spam complaints suppress automatically; a `SubscriptionChange` webhook tells you when one is added or removed.
|
|
13
|
+
|
|
14
|
+
## Amazon SES (API v2)
|
|
15
|
+
|
|
16
|
+
- Send: `SendEmail` (SDK `SESv2Client` + `SendEmailCommand`) with `Content.Simple`, `Content.Template` (stored template) or `Content.Raw`, `ConfigurationSetName`, and `EmailTags` for your own message key.
|
|
17
|
+
- Sandbox (per Region): send only to verified addresses or the mailbox simulator, at most 200 messages per 24 hours and 1 per second. Request production access per Region.
|
|
18
|
+
- Mailbox simulator (works in the sandbox, does not hurt reputation, still billed): `success@simulator.amazonses.com`, `bounce@`, `complaint@`, `ooto@`, `suppressionlist@`. Add labels (`bounce+order123@simulator.amazonses.com`) to match events to messages.
|
|
19
|
+
- Events: configuration set event destinations to SNS, EventBridge, Firehose or CloudWatch. For SNS over HTTPS, confirm the `SubscriptionConfirmation` and verify every message's SNS signature (the AWS SDK message validator) before trusting it; also check the topic ARN.
|
|
20
|
+
- Account-level suppression list (on by default for bounces and complaints in accounts created after 25 Nov 2019; enable older ones with `aws sesv2 put-account-suppression-attributes --suppressed-reasons BOUNCE COMPLAINT`); manage with `PutSuppressedDestination`, `DeleteSuppressedDestination`, `ListSuppressedDestinations`. Only hard bounces (not soft) are added. Sending treats address case as equal, but the suppression management API needs the exact case stored, so keep the original address alongside your normalised one. Configuration sets can override it. Messages to suppressed addresses are accepted, not delivered, and still count toward the daily sending quota.
|
|
21
|
+
- Gmail does not send complaint feedback to SES, so a Gmail "Report spam" will not reach your suppression list.
|
|
22
|
+
- Idempotency: none in the API. Rely on your outbox key.
|
|
23
|
+
|
|
24
|
+
## Resend
|
|
25
|
+
|
|
26
|
+
- Send: `POST https://api.resend.com/emails` with `from`, `to`, `subject`, `html`/`text` or `react` (Node SDK), `tags`. Header `Authorization: Bearer re_...`.
|
|
27
|
+
- Idempotency: `Idempotency-Key` header (SMTP: `Resend-Idempotency-Key`), 1-256 chars, kept 24 hours. Same key with a different payload returns `409 invalid_idempotent_request`; a request still in flight returns `409 concurrent_idempotent_requests` (safe to retry later).
|
|
28
|
+
- Test addresses: `delivered@resend.dev`, `bounced@resend.dev`, `complained@resend.dev` (these three accept `+label`, e.g. `bounced+signup1@resend.dev`) and `suppressed@resend.dev` (no labels). Test sends count against the account's sending quota.
|
|
29
|
+
- Webhooks are signed via Svix: headers `svix-id`, `svix-timestamp`, `svix-signature`; verify the raw body string with `resend.webhooks.verify` or the `svix` library `Webhook` class. Email events: `email.sent`, `email.delivered`, `email.delivery_delayed`, `email.bounced`, `email.complained`, `email.failed`, `email.suppressed`, `email.opened`, `email.clicked`, `email.scheduled`, `email.received`. Suppress on `email.bounced` and `email.complained`; mark the outbox row on `email.failed` and `email.suppressed`.
|
|
30
|
+
|
|
31
|
+
## Mailtrap
|
|
32
|
+
|
|
33
|
+
- Testing: a sandbox inbox catches everything; nothing reaches real recipients. API: `POST https://sandbox.api.mailtrap.io/api/send/{inbox_id}` with the `Api-Token` header (or bearer token); SMTP credentials come from the inbox's Integration tab. Use it for development and staging, including automated tests that read the inbox through the API.
|
|
34
|
+
- Sending: transactional sending API for production with a separate token from the testing token. Check the current host and stream (transactional versus bulk) in the Mailtrap docs.
|
|
35
|
+
|
|
36
|
+
## SendGrid (Twilio)
|
|
37
|
+
|
|
38
|
+
- Send: `POST https://api.sendgrid.com/v3/mail/send` with `personalizations[]`, `from`, `template_id` (`d-...` dynamic template) and `personalizations[].dynamic_template_data`, `custom_args` for your message key, `asm.group_id` for an unsubscribe group. `202 Accepted` on success.
|
|
39
|
+
- Sandbox mode: `"mail_settings": { "sandbox_mode": { "enable": true } }` validates and returns `200 OK` without delivering. The boolean must not be quoted.
|
|
40
|
+
- Signed Event Webhook: ECDSA signature in `X-Twilio-Email-Event-Webhook-Signature`, timestamp in `X-Twilio-Email-Event-Webhook-Timestamp`; verify over timestamp + raw body with the public key from the webhook settings, using the helper library. OAuth 2.0 client credentials can be enabled as well.
|
|
41
|
+
- Events arrive batched as a JSON array: `processed`, `dropped`, `delivered`, `deferred`, `bounce`, `open`, `click`, `spamreport`, `unsubscribe`, `group_unsubscribe`. Each has `sg_event_id`; dedupe on it.
|
|
42
|
+
- Suppressions: bounces, blocks, spam reports and invalid emails each have a `/v3/suppression/{bounces|blocks|spam_reports|invalid_emails}` endpoint; global unsubscribes are managed at `/v3/asm/suppressions/global` and unsubscribe-group suppressions at `/v3/asm/groups/{group_id}/suppressions`.
|
|
43
|
+
- Idempotency: none in the API. Rely on your outbox key.
|
|
44
|
+
|
|
45
|
+
## Webhook handler shape (any provider)
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
app.post("/webhooks/email", express.raw({ type: "*/*" }), async (req, res) => {
|
|
49
|
+
let events;
|
|
50
|
+
try {
|
|
51
|
+
events = verifyAndParse(req.headers, req.body); // provider-specific; returns an array (SendGrid batches events)
|
|
52
|
+
} catch {
|
|
53
|
+
return res.sendStatus(401); // bad signature or credentials: reject, never process
|
|
54
|
+
}
|
|
55
|
+
for (const event of events) {
|
|
56
|
+
const inserted = await db.emailEvents.insertIfAbsent({ id: event.id, provider, raw: JSON.stringify(event) });
|
|
57
|
+
if (inserted) await queue.add("email-event", { id: event.id });
|
|
58
|
+
}
|
|
59
|
+
res.sendStatus(200); // only after every event is stored, so a crash mid-batch gets a retry
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Map in the job: hard bounce or complaint → upsert `email_suppressions(address_normalised, reason, event_id, created_at)`; soft bounce → log and count; delivered → mark the outbox row.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-demo-recording
|
|
3
|
+
description: Produces polished, repeatable product demo videos and feature walkthroughs of a web app by scripting the story as a Playwright run with recordVideo, seeding fixed demo data and a frozen clock, pacing actions for a human viewer, injecting a visible cursor and click ripples, adding captions or chapter titles, then trimming, scaling and encoding the WebM with FFmpeg into MP4 or GIF. Use when marketing, docs or a release note needs a screen recording of the app, when an existing demo video is out of date after a UI change, or when the same walkthrough must be re-recorded on every release.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Needs Node 18+, the @playwright/test or playwright package with a Chromium browser installed, and ffmpeg. The app must run locally or on a staging URL with demo data.
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: coding
|
|
9
|
+
kete-loom/risk: runs-commands
|
|
10
|
+
kete-loom/requires: "node, npx, ffmpeg"
|
|
11
|
+
kete-loom/owner: "Kete-org"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# UI demo recording
|
|
15
|
+
|
|
16
|
+
A demo video is a script, not a screen grab. Write the walkthrough as code so it records identically every time, looks like a calm human is driving, and can be re-run after the next UI change. This skill makes videos for people to watch; for tests that assert behaviour, use the `e2e-testing-playwright` skill.
|
|
17
|
+
|
|
18
|
+
## 0. Agree the storyboard
|
|
19
|
+
|
|
20
|
+
Ask the user (stop until you have answers):
|
|
21
|
+
|
|
22
|
+
1. **Story**: the 3 to 7 steps the viewer should see, and the one outcome to end on.
|
|
23
|
+
2. **Output**: length target, resolution (1920x1080 or 1280x720 for docs, 1080x1920 for vertical), MP4, WebM or GIF, with or without captions.
|
|
24
|
+
3. **Environment**: local or staging URL, a demo account, and confirmation that **no real customer data** appears.
|
|
25
|
+
4. **Branding**: light or dark theme, browser chrome or none (Playwright records the page only, without browser chrome).
|
|
26
|
+
|
|
27
|
+
Write the storyboard as a numbered list of scenes, each with an action, the on-screen caption and a hold time. Get a yes before recording.
|
|
28
|
+
|
|
29
|
+
## 1. Make the app deterministic
|
|
30
|
+
|
|
31
|
+
The recording must look the same on every run.
|
|
32
|
+
|
|
33
|
+
- **Data**: seed a dedicated demo tenant or database with believable fake names, round numbers and no lorem ipsum. Reset it before each recording.
|
|
34
|
+
- **Time**: freeze the clock so dates and "5 minutes ago" labels are stable (`page.clock.setFixedTime` in Playwright 1.45+; see `references/playwright-recording.md`).
|
|
35
|
+
- **Network noise**: block analytics, chat widgets and cookie banners with `page.route`, or set the consent cookie in advance.
|
|
36
|
+
- **Randomness**: disable onboarding tours, A/B variants and "what's new" pop-ups through feature flags or localStorage before the first navigation.
|
|
37
|
+
- **Login**: sign in once in setup and save `storageState`; never record typing a real password.
|
|
38
|
+
- **Animations**: keep the app's own animations (they look good on video), but wait for them to finish before the next action.
|
|
39
|
+
|
|
40
|
+
## 2. Write the recording script
|
|
41
|
+
|
|
42
|
+
Use a plain Playwright library script (or a dedicated Playwright Test project) separate from the test suite. Skeleton in `references/playwright-recording.md`.
|
|
43
|
+
|
|
44
|
+
1. Create the context with `viewport` equal to the output size and `recordVideo: { dir, size }` set to the same size, plus `deviceScaleFactor: 1`. A mismatch gives blurry or letterboxed video.
|
|
45
|
+
2. Inject the cursor overlay with `context.addInitScript` before the first page loads (`references/cursor-and-captions.md`).
|
|
46
|
+
3. Drive each scene with role-based locators, moving the mouse to the target first (`page.mouse.move` with `steps`) so the viewer sees the pointer travel, then click.
|
|
47
|
+
4. Type with `locator.pressSequentially(text, { delay: 40 to 80 })` so typing is visible; use `fill` for fields the viewer does not need to watch.
|
|
48
|
+
5. Hold after each meaningful change (`page.waitForTimeout(800 to 1500)`). Holds are fine here because this is a recording, not a test; always wait for the visible result first (`expect(locator).toBeVisible()` or `locator.waitFor()`).
|
|
49
|
+
6. Show captions or chapter titles through the overlay, or leave them for post-production (step 4).
|
|
50
|
+
7. Close the context (`await context.close()`): the video file is only finished then. Get the path from `page.video().path()` or `saveAs`.
|
|
51
|
+
|
|
52
|
+
## 3. Review the raw take
|
|
53
|
+
|
|
54
|
+
Play it back at full speed and check:
|
|
55
|
+
|
|
56
|
+
- [ ] Every scene from the storyboard appears in order, nothing extra.
|
|
57
|
+
- [ ] No spinners, flashes of unstyled content, error toasts, scrollbars jumping or debug panels.
|
|
58
|
+
- [ ] No real personal data, internal URLs, API keys or staff names visible.
|
|
59
|
+
- [ ] The cursor is visible and never teleports.
|
|
60
|
+
|
|
61
|
+
If a scene is flaky, fix the wait in the script; do not cut around it in the edit.
|
|
62
|
+
|
|
63
|
+
## 4. Post-process with FFmpeg
|
|
64
|
+
|
|
65
|
+
Recipes in `references/post-processing.md`:
|
|
66
|
+
|
|
67
|
+
1. Trim the blank first frames and the tail after the last hold.
|
|
68
|
+
2. Convert WebM to H.264 MP4 with `yuv420p` and `+faststart` for the web.
|
|
69
|
+
3. Optionally speed up dull stretches, add a title card or fade, burn in or attach captions (SRT or WebVTT), and add a music bed at low level if the user supplies a licensed track.
|
|
70
|
+
4. For GIF, use the two-pass palette method, at most about 15 fps and 800 px wide, and keep it short.
|
|
71
|
+
5. Probe the output (duration, resolution, size) before handing it over.
|
|
72
|
+
|
|
73
|
+
## 5. Make it repeatable
|
|
74
|
+
|
|
75
|
+
- Commit the script, the seed data and the storyboard next to each other (for example `demos/<name>/`).
|
|
76
|
+
- Add an npm script such as `"demo:onboarding": "node demos/onboarding/record.mjs"`.
|
|
77
|
+
- If the user wants it refreshed each release, run it in CI against staging and upload the MP4 as an artifact; a human approves before it replaces the published video.
|
|
78
|
+
|
|
79
|
+
## Never
|
|
80
|
+
|
|
81
|
+
- Never record production with real customer accounts or data.
|
|
82
|
+
- Never leave real credentials in the script; read them from the environment for a demo-only account.
|
|
83
|
+
- Never publish a recording without the user watching it first.
|
|
84
|
+
|
|
85
|
+
## Report back
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
Demo: <name> Length: mm:ss Output: <path> (WxH, fps, MB)
|
|
89
|
+
Script: <path> Seed: <path> Run: <command>
|
|
90
|
+
Scenes: 1 ... 2 ... (one line each with start time)
|
|
91
|
+
Checks: data is fake, no secrets on screen, cursor visible
|
|
92
|
+
Known gaps: ...
|
|
93
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Record a 60-second demo video of our signup and first project flow for the landing page",
|
|
4
|
+
"Our product walkthrough video is out of date after the redesign, script it so we can re-record it every release",
|
|
5
|
+
"Make a Playwright script that records the dashboard demo with a visible cursor and click highlights",
|
|
6
|
+
"Turn the recorded WebM of our app tour into an MP4 and a small GIF for the release notes",
|
|
7
|
+
"Capture a screencast of the checkout feature with fake demo data and captions explaining each step"
|
|
8
|
+
],
|
|
9
|
+
"should_not_trigger": [
|
|
10
|
+
"Write Playwright tests that check the checkout flow still works after each deploy",
|
|
11
|
+
"Our Playwright suite fails in CI on the login test, find out why",
|
|
12
|
+
"Cut my interview footage into a three-minute story with B-roll",
|
|
13
|
+
"Build an animated explainer video in React with spring animations",
|
|
14
|
+
"Click through the staging app and look for bugs in the settings page"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Cursor highlight and caption overlay
|
|
2
|
+
|
|
3
|
+
Headless Chromium does not draw the OS cursor into the video, so draw one in the page. Pass this function to `context.addInitScript` so it runs before every page's scripts.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
// demos/onboarding/cursor-overlay.mjs
|
|
7
|
+
export function cursorOverlay() {
|
|
8
|
+
const install = () => {
|
|
9
|
+
if (document.getElementById('__demo-cursor')) return;
|
|
10
|
+
const style = document.createElement('style');
|
|
11
|
+
style.textContent = `
|
|
12
|
+
#__demo-cursor { position: fixed; top: 0; left: 0; width: 22px; height: 22px; margin: -11px 0 0 -11px;
|
|
13
|
+
border-radius: 50%; background: rgba(255,255,255,.9); border: 2px solid rgba(0,0,0,.6);
|
|
14
|
+
box-shadow: 0 1px 4px rgba(0,0,0,.35); pointer-events: none; z-index: 2147483647;
|
|
15
|
+
transition: transform .08s ease-out; }
|
|
16
|
+
#__demo-cursor.down { transform: scale(.75); }
|
|
17
|
+
.__demo-ripple { position: fixed; width: 44px; height: 44px; margin: -22px 0 0 -22px; border-radius: 50%;
|
|
18
|
+
border: 3px solid rgba(59,130,246,.8); pointer-events: none; z-index: 2147483646;
|
|
19
|
+
animation: __demo-r .5s ease-out forwards; }
|
|
20
|
+
@keyframes __demo-r { from { transform: scale(.3); opacity: 1 } to { transform: scale(1.4); opacity: 0 } }
|
|
21
|
+
#__demo-caption { position: fixed; left: 50%; bottom: 48px; transform: translateX(-50%); max-width: 70%;
|
|
22
|
+
padding: 12px 20px; border-radius: 10px; background: rgba(17,24,39,.85); color: #fff;
|
|
23
|
+
font: 600 28px/1.3 system-ui, sans-serif; text-align: center; pointer-events: none;
|
|
24
|
+
z-index: 2147483647; opacity: 0; transition: opacity .25s; }
|
|
25
|
+
#__demo-caption.on { opacity: 1; }`;
|
|
26
|
+
document.head.appendChild(style);
|
|
27
|
+
const c = document.createElement('div'); c.id = '__demo-cursor';
|
|
28
|
+
const cap = document.createElement('div'); cap.id = '__demo-caption';
|
|
29
|
+
document.body.append(c, cap);
|
|
30
|
+
addEventListener('mousemove', (e) => { c.style.left = e.clientX + 'px'; c.style.top = e.clientY + 'px'; }, true);
|
|
31
|
+
addEventListener('mousedown', (e) => {
|
|
32
|
+
c.classList.add('down');
|
|
33
|
+
const r = document.createElement('div'); r.className = '__demo-ripple';
|
|
34
|
+
r.style.left = e.clientX + 'px'; r.style.top = e.clientY + 'px';
|
|
35
|
+
document.body.appendChild(r); setTimeout(() => r.remove(), 600);
|
|
36
|
+
}, true);
|
|
37
|
+
addEventListener('mouseup', () => c.classList.remove('down'), true);
|
|
38
|
+
window.__demoCaption = (t) => { cap.textContent = t; cap.classList.toggle('on', !!t); };
|
|
39
|
+
};
|
|
40
|
+
if (document.body) install(); else addEventListener('DOMContentLoaded', install);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Rules:
|
|
45
|
+
- The overlay has `pointer-events: none` so it never intercepts clicks.
|
|
46
|
+
- Keep captions short (one line, under about 50 characters) and on screen for at least 1.5 seconds.
|
|
47
|
+
- If captions must be translatable or editable later, produce an SRT in post-processing instead of burning them into the page.
|
|
48
|
+
- A strict Content-Security-Policy can block injected styles in some apps; if the overlay is missing, run against a demo build that relaxes CSP, or use `bypassCSP: true` on the context.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Playwright recording script
|
|
2
|
+
|
|
3
|
+
Video is written when the browser context closes. Without an explicit size, Playwright scales the viewport down to fit in 800x800, so always set both `viewport` and `recordVideo.size`.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
// demos/onboarding/record.mjs
|
|
7
|
+
import { chromium } from 'playwright';
|
|
8
|
+
import { cursorOverlay } from './cursor-overlay.mjs';
|
|
9
|
+
|
|
10
|
+
const W = 1920, H = 1080;
|
|
11
|
+
const BASE = process.env.DEMO_URL ?? 'http://localhost:3000';
|
|
12
|
+
|
|
13
|
+
const browser = await chromium.launch();
|
|
14
|
+
const context = await browser.newContext({
|
|
15
|
+
viewport: { width: W, height: H },
|
|
16
|
+
deviceScaleFactor: 1,
|
|
17
|
+
colorScheme: 'light',
|
|
18
|
+
locale: 'en-GB',
|
|
19
|
+
timezoneId: 'Europe/London',
|
|
20
|
+
storageState: 'demos/.auth/demo-user.json',
|
|
21
|
+
recordVideo: { dir: 'demos/onboarding/raw', size: { width: W, height: H } },
|
|
22
|
+
});
|
|
23
|
+
await context.addInitScript(cursorOverlay);
|
|
24
|
+
await context.route(/(google-analytics|segment|intercom|hotjar)/, (r) => r.abort());
|
|
25
|
+
|
|
26
|
+
const page = await context.newPage();
|
|
27
|
+
await page.clock.setFixedTime(new Date('2026-03-02T10:00:00Z'));
|
|
28
|
+
|
|
29
|
+
async function clickLike(locator) {
|
|
30
|
+
await locator.waitFor({ state: 'visible' });
|
|
31
|
+
const box = await locator.boundingBox();
|
|
32
|
+
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 25 });
|
|
33
|
+
await page.waitForTimeout(200);
|
|
34
|
+
await locator.click();
|
|
35
|
+
}
|
|
36
|
+
const hold = (ms = 1200) => page.waitForTimeout(ms);
|
|
37
|
+
const caption = (text) => page.evaluate((t) => window.__demoCaption?.(t), text);
|
|
38
|
+
|
|
39
|
+
// Scene 1
|
|
40
|
+
await page.goto(`${BASE}/projects`);
|
|
41
|
+
await caption('Create a project in seconds');
|
|
42
|
+
await hold();
|
|
43
|
+
|
|
44
|
+
// Scene 2
|
|
45
|
+
await clickLike(page.getByRole('button', { name: 'New project' }));
|
|
46
|
+
await page.getByLabel('Project name').pressSequentially('Harbour Bridge refit', { delay: 60 });
|
|
47
|
+
await hold(600);
|
|
48
|
+
await clickLike(page.getByRole('button', { name: 'Create' }));
|
|
49
|
+
await page.getByRole('heading', { name: 'Harbour Bridge refit' }).waitFor();
|
|
50
|
+
await caption('');
|
|
51
|
+
await hold(1500);
|
|
52
|
+
|
|
53
|
+
const video = page.video();
|
|
54
|
+
await context.close(); // finishes the file
|
|
55
|
+
await video.saveAs('demos/onboarding/raw/take.webm');
|
|
56
|
+
await browser.close();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Notes:
|
|
60
|
+
- `page.clock` exists in Playwright 1.45 and later; on older versions, override `Date` with an init script.
|
|
61
|
+
- With Playwright Test, set `use: { video: { mode: 'on', size: { width: 1920, height: 1080 } }, viewport: { width: 1920, height: 1080 } }` in a separate `demo` project. Recent versions also accept `video.show.actions` (outline and title for each action) and `video.show.test`; these are debugging annotations, usually unwanted in a marketing demo, and missing in older versions.
|
|
62
|
+
- Chromium gives the most consistent recordings; record in headless mode on the same machine type each time.
|
|
63
|
+
- Record a fresh take for each language or theme instead of editing one video.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Post-processing a Playwright take
|
|
2
|
+
|
|
3
|
+
Playwright writes VP8 WebM. Probe it first: `ffprobe -v error -show_entries format=duration:stream=width,height,r_frame_rate -of json take.webm`.
|
|
4
|
+
|
|
5
|
+
## Trim and convert to web MP4
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
ffmpeg -ss 0.6 -i take.webm -t 40.4 -vf "fps=30,format=yuv420p" -c:v libx264 -crf 18 -preset slow -movflags +faststart -an demo.mp4
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`-ss` before `-i` with re-encode is frame-accurate; give the output length with `-t` (seconds kept after the start point), since `-to` combined with input seeking is easy to misread. Drop `-an` if you add audio below.
|
|
12
|
+
|
|
13
|
+
## Find dead frames to trim
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
ffmpeg -i take.webm -vf "freezedetect=n=0.001:d=2" -an -f null - 2>&1 | grep freeze_
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Speed up a dull stretch (10 s to 18 s at 2x)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
ffmpeg -i demo.mp4 -filter_complex "[0:v]trim=start=0:end=10,setpts=PTS-STARTPTS[a];[0:v]trim=start=10:end=18,setpts=(PTS-STARTPTS)/2[b];[0:v]trim=start=18,setpts=PTS-STARTPTS[c];[a][b][c]concat=n=3:v=1:a=0[v]" -map "[v]" -c:v libx264 -crf 18 -pix_fmt yuv420p demo_fast.mp4
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Fade in and out
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
ffmpeg -i demo.mp4 -vf "fade=t=in:st=0:d=0.4,fade=t=out:st=40.4:d=0.6" -c:v libx264 -crf 18 -pix_fmt yuv420p demo_fade.mp4
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Captions
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# soft captions (keeps them accessible and editable)
|
|
35
|
+
ffmpeg -i demo.mp4 -i demo.srt -map 0 -map 1 -c copy -c:s mov_text -metadata:s:s:0 language=eng demo_cc.mp4
|
|
36
|
+
# burned in
|
|
37
|
+
ffmpeg -i demo.mp4 -vf "subtitles=demo.srt:force_style='FontSize=22,Outline=1'" -c:v libx264 -crf 18 demo_burned.mp4
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Music bed (licensed track supplied by the user)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
ffmpeg -i demo.mp4 -i bed.mp3 -filter_complex "[1:a]volume=0.25,afade=t=out:st=38:d=3[m]" -map 0:v -map "[m]" -shortest -c:v copy -c:a aac -b:a 160k demo_music.mp4
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## GIF (two-pass palette)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
ffmpeg -i demo.mp4 -vf "fps=12,scale=800:-1:flags=lanczos,palettegen=stats_mode=diff" palette.png
|
|
50
|
+
ffmpeg -i demo.mp4 -i palette.png -lavfi "fps=12,scale=800:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=5" demo.gif
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Final check
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
ffprobe -v error -show_entries format=duration,size:stream=codec_name,width,height,pix_fmt -of compact demo.mp4
|
|
57
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-design-polish
|
|
3
|
+
description: Visual design direction and design-engineering finish for product UI - choosing a deliberate aesthetic instead of template defaults, then applying a spacing scale, a modular type scale, semantic colour tokens, hover, focus, active and disabled states, optical alignment, and real empty, loading and error states; also inventories an existing codebase's ad-hoc colours, sizes and components into a design-system audit. Use when a screen looks generic, unfinished or inconsistent, someone asks to make the UI look better or more professional, or wants the de facto design system extracted from scattered styles.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Works with plain CSS, CSS Modules, Tailwind or CSS-in-JS. Running the app and a browser for screenshots helps but is optional.
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: coding
|
|
9
|
+
kete-loom/risk: writes-files
|
|
10
|
+
kete-loom/owner: "Kete-org"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# UI design polish
|
|
14
|
+
|
|
15
|
+
Make an interface look intended. Two jobs: pick a visual direction and hold it, then sweat the details that separate "works" from "finished". Most "it looks off" complaints are inconsistent spacing, too many font sizes, unmanaged colour and missing states, not a lack of decoration.
|
|
16
|
+
|
|
17
|
+
Neighbours: `css-architecture` decides how styles are organised (layers, naming, theming plumbing); `web-accessibility` owns WCAG conformance; `ui-motion` owns animation. This skill decides what things look like.
|
|
18
|
+
|
|
19
|
+
## 0. Before you start
|
|
20
|
+
|
|
21
|
+
1. Ask, or infer from the product, three things: **who uses it**, **the tone** (calm and dense for a tool, warm for consumer, editorial for content), and **constraints** (existing brand, component library, dark mode). If there is a brand guide or design file, it wins; **stop and ask** before overriding it.
|
|
22
|
+
2. Find the current styling approach and any token file (`tokens.css`, `theme.ts`, `tailwind.config.*` `theme.extend`, CSS custom properties on `:root`). Polish goes through tokens, not one-off values.
|
|
23
|
+
3. Look at the real screens. Run the app if you can and capture the screens you will change, at mobile and desktop widths, before touching anything.
|
|
24
|
+
|
|
25
|
+
## 1. Choose a direction (new UI or redesign)
|
|
26
|
+
|
|
27
|
+
Write the direction down in 4-6 lines before coding, for example:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Direction: quiet utility. Neutral warm greys, one accent (teal 600) used only for primary actions and focus.
|
|
31
|
+
Type: one sans (system stack or Inter), 4 sizes; weight carries hierarchy, not colour.
|
|
32
|
+
Density: compact, 4px base grid, 8/12/16/24/32 spacing.
|
|
33
|
+
Shape: 6px radius on controls, 10px on cards; borders over shadows.
|
|
34
|
+
Signature: tabular numbers and a thin top rule on section headers.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Rules:
|
|
38
|
+
|
|
39
|
+
- One accent colour, used sparingly. If everything is highlighted, nothing is.
|
|
40
|
+
- Pick one signature detail and repeat it; do not add five.
|
|
41
|
+
- Avoid the template look: centred hero with gradient blob, three equal cards with icons, purple-to-blue gradients, everything rounded-2xl with a drop shadow. Use them only if the brief asks.
|
|
42
|
+
- Restraint beats decoration. Remove before adding.
|
|
43
|
+
|
|
44
|
+
## 2. Foundations as tokens
|
|
45
|
+
|
|
46
|
+
Set these up (or tighten the existing ones). Value tables and naming in `references/tokens.md`.
|
|
47
|
+
|
|
48
|
+
- **Spacing**: a scale on a 4px or 8px base. Every margin, padding and gap uses it. Related things sit closer together than unrelated things.
|
|
49
|
+
- **Type**: 4-6 sizes from a ratio (1.2-1.25 for dense UI, up to 1.333 for marketing). Body 16px on the web, line-height 1.4-1.6 for body and 1.1-1.25 for headings, measure 60-75 characters.
|
|
50
|
+
- **Colour**: raw palette (`--gray-50`...`--gray-950`, one accent ramp, red/amber/green for status) mapped to **semantic** tokens (`--color-bg`, `--color-surface`, `--color-text`, `--color-text-muted`, `--color-border`, `--color-accent`, `--color-danger`). Components only use semantic tokens. Dark mode redefines the semantic layer.
|
|
51
|
+
- **Radius, border, shadow, z-index**: 2-3 values each.
|
|
52
|
+
|
|
53
|
+
Check text contrast against WCAG AA (4.5:1 body, 3:1 large text and UI component boundaries) as you choose colours.
|
|
54
|
+
|
|
55
|
+
## 3. Interactive states
|
|
56
|
+
|
|
57
|
+
Every control needs all of these, designed, not browser defaults left by accident:
|
|
58
|
+
|
|
59
|
+
| State | Rule |
|
|
60
|
+
|---|---|
|
|
61
|
+
| Default | Clear affordance: a button looks pressable, a link looks like a link |
|
|
62
|
+
| Hover | Subtle shift (background one step on the ramp); only on `@media (hover: hover)` devices |
|
|
63
|
+
| Focus-visible | A visible ring (`:focus-visible`, 2px outline with offset, accent or high-contrast colour). Never `outline: none` without a replacement |
|
|
64
|
+
| Active / pressed | Slightly darker or inset; immediate |
|
|
65
|
+
| Disabled | Reduced contrast plus `cursor: not-allowed`; still readable; explain why nearby if it is not obvious |
|
|
66
|
+
| Loading | Keep the button's width; spinner or label change; block double submit |
|
|
67
|
+
| Selected / current | Distinct from hover (`aria-current`, `aria-selected` styled) |
|
|
68
|
+
| Invalid | Border and message in danger colour plus an icon or text, never colour alone |
|
|
69
|
+
|
|
70
|
+
## 4. Screen states
|
|
71
|
+
|
|
72
|
+
For each data-driven view, design: **loading** (skeleton matching the final layout, not a page-centre spinner, for waits over ~300ms), **empty** (what this is, why it is empty, the one action that fills it), **error** (what happened in plain words, what to do, a retry), **partial** (some items failed), and **overflow** (long names, 0 / 1 / 1,000 items, missing images). Write the copy too.
|
|
73
|
+
|
|
74
|
+
## 5. Detail pass
|
|
75
|
+
|
|
76
|
+
Go through `references/detail-checklist.md` on each changed screen. The highest-yield items:
|
|
77
|
+
|
|
78
|
+
- Align to a grid; left edges line up across sections.
|
|
79
|
+
- Optical alignment: icons next to text are centred on the text's x-height or cap height, not the box; play icons nudge right; round shapes overshoot squares slightly.
|
|
80
|
+
- Tabular numbers (`font-variant-numeric: tabular-nums`) in tables, prices and timers.
|
|
81
|
+
- Consistent icon set, size and stroke weight.
|
|
82
|
+
- No orphaned single words in headings where `text-wrap: balance` can fix it.
|
|
83
|
+
- Hit targets at least 24x24 CSS px (44x44 on touch-first screens).
|
|
84
|
+
|
|
85
|
+
## 6. Design-system audit (existing codebase)
|
|
86
|
+
|
|
87
|
+
When asked to extract or audit the design system:
|
|
88
|
+
|
|
89
|
+
1. Search the styles for raw values: hex/rgb/hsl colours, `px`/`rem` font sizes, spacing values, radii, shadows, z-indexes, breakpoints. Count each distinct value and where it is used.
|
|
90
|
+
2. Cluster near-duplicates (`#1f2937` vs `#1e293b`, `14px` vs `0.875rem` vs `13.5px`).
|
|
91
|
+
3. Propose the token set: the clusters that survive, with a mapping from each old value to its token.
|
|
92
|
+
4. Inventory components: list every button, input, card and modal variant found, with file paths, and mark duplicates.
|
|
93
|
+
5. Report with the format below. **Stop and ask** before a mass find-and-replace; do it per component or per page so diffs stay reviewable.
|
|
94
|
+
|
|
95
|
+
## Never
|
|
96
|
+
|
|
97
|
+
- Never introduce a new colour, size or spacing value that is not a token.
|
|
98
|
+
- Never remove focus styles, or rely on colour alone for meaning.
|
|
99
|
+
- Never restyle a brand's logo, colours or fonts without the owner's approval.
|
|
100
|
+
- Never ship a visual change you have not looked at in a rendered page at mobile and desktop widths, in light and dark if both exist.
|
|
101
|
+
|
|
102
|
+
## Report back
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
## UI polish: <screen or scope>
|
|
106
|
+
Direction: <the 4-6 line direction, or "kept existing">
|
|
107
|
+
Tokens added/changed: <list>
|
|
108
|
+
Changes: <file: what changed and why, one line each>
|
|
109
|
+
States covered: <loading / empty / error / hover / focus / disabled per component>
|
|
110
|
+
Before/after: <screenshot paths, or "not captured">
|
|
111
|
+
Audit (if run): <N colours -> M tokens, N font sizes -> M, duplicate components found>
|
|
112
|
+
Left for later: <items and why>
|
|
113
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Our settings page works but looks amateur, tighten the spacing, type and colours so it feels professional",
|
|
4
|
+
"This dashboard looks like every other AI-generated template, give it a distinct visual direction",
|
|
5
|
+
"Go through the codebase and tell me how many different greys, font sizes and button styles we actually use, then propose a token set",
|
|
6
|
+
"The projects list has no empty state, loading state or error state, design all three",
|
|
7
|
+
"Polish the buttons: hover, pressed, disabled and loading states all look like browser defaults",
|
|
8
|
+
"The icons and labels in the sidebar look slightly misaligned, fix the optical alignment and spacing"
|
|
9
|
+
],
|
|
10
|
+
"should_not_trigger": [
|
|
11
|
+
"Introduce cascade layers so the vendor CSS stops overriding our component styles",
|
|
12
|
+
"Audit the signup form against WCAG 2.2 AA and fix the screen reader problems",
|
|
13
|
+
"Add a spring animation when the modal opens and a fade when it closes",
|
|
14
|
+
"Write a brand voice guide for our marketing emails",
|
|
15
|
+
"Our homepage LCP is 4 seconds, make it load faster",
|
|
16
|
+
"Build a slide deck for Friday's product review"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Detail checklist
|
|
2
|
+
|
|
3
|
+
Run per changed screen. Tick what passes, fix or list what does not.
|
|
4
|
+
|
|
5
|
+
## Layout and spacing
|
|
6
|
+
- [ ] Every gap, margin and padding is on the spacing scale.
|
|
7
|
+
- [ ] Proximity groups related items; space between groups is larger than within.
|
|
8
|
+
- [ ] Left edges align across header, content and footer.
|
|
9
|
+
- [ ] Content width is capped for reading (about 65ch); wide tables scroll inside their container, not the page.
|
|
10
|
+
- [ ] Nothing touches the viewport edge at 320px width; no horizontal page scroll.
|
|
11
|
+
|
|
12
|
+
## Typography
|
|
13
|
+
- [ ] No more than the scale's sizes; no stray 13px or 15px.
|
|
14
|
+
- [ ] Hierarchy is readable with colour removed (size and weight carry it).
|
|
15
|
+
- [ ] Line length and line height suit the text size.
|
|
16
|
+
- [ ] Headings use `text-wrap: balance`; paragraphs may use `text-wrap: pretty`.
|
|
17
|
+
- [ ] Numbers in tables, prices and counters use `tabular-nums`; right-aligned in numeric columns.
|
|
18
|
+
- [ ] Truncation has a full-text fallback (tooltip, title or expand) and does not cut mid-character.
|
|
19
|
+
|
|
20
|
+
## Colour
|
|
21
|
+
- [ ] Only semantic tokens in components.
|
|
22
|
+
- [ ] Accent is used for the primary action and focus, not decoration.
|
|
23
|
+
- [ ] Text contrast meets 4.5:1 (body) and 3:1 (large text, control borders, icons that carry meaning).
|
|
24
|
+
- [ ] Status is never shown by colour alone.
|
|
25
|
+
|
|
26
|
+
## Alignment details
|
|
27
|
+
- [ ] Icons are optically centred with their label (adjust by 1px when the glyph box misleads).
|
|
28
|
+
- [ ] Icon size matches text size (16px icon with 14-16px text, 20px with 18-20px).
|
|
29
|
+
- [ ] Inputs, buttons and selects in the same row share a height.
|
|
30
|
+
- [ ] Nested radii are concentric (inner = outer - padding).
|
|
31
|
+
- [ ] Borders are 1px and the same colour token; no double borders where cards meet.
|
|
32
|
+
|
|
33
|
+
## States
|
|
34
|
+
- [ ] Hover, focus-visible, active, disabled, loading, selected and invalid exist for each control.
|
|
35
|
+
- [ ] Loading skeleton matches the final layout; no layout shift when data arrives.
|
|
36
|
+
- [ ] Empty state explains and offers the next action.
|
|
37
|
+
- [ ] Error state says what happened and how to recover, with retry.
|
|
38
|
+
- [ ] Long, short, zero, one and many items all render well.
|
|
39
|
+
|
|
40
|
+
## Polish
|
|
41
|
+
- [ ] Images have fixed aspect ratios (`aspect-ratio`) and a placeholder background.
|
|
42
|
+
- [ ] Cursor is `pointer` only on clickable elements.
|
|
43
|
+
- [ ] Scrollbars and overflow do not shift the layout (`scrollbar-gutter: stable` where needed).
|
|
44
|
+
- [ ] Selection colour and caret colour suit the theme (`::selection`, `caret-color`), when the direction calls for it.
|
|
45
|
+
- [ ] Favicon, page title and social preview exist for public pages.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Token starting points
|
|
2
|
+
|
|
3
|
+
Adjust to the direction; these are defaults, not rules.
|
|
4
|
+
|
|
5
|
+
## Spacing (4px base)
|
|
6
|
+
|
|
7
|
+
| Token | px | Typical use |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `--space-1` | 4 | icon-to-label gap |
|
|
10
|
+
| `--space-2` | 8 | inside compact controls, tight stacks |
|
|
11
|
+
| `--space-3` | 12 | control padding, list item gap |
|
|
12
|
+
| `--space-4` | 16 | card padding (dense), form field gap |
|
|
13
|
+
| `--space-6` | 24 | card padding, group gap |
|
|
14
|
+
| `--space-8` | 32 | section gap |
|
|
15
|
+
| `--space-12` | 48 | page section gap |
|
|
16
|
+
| `--space-16` | 64 | hero / marketing sections |
|
|
17
|
+
|
|
18
|
+
Tailwind's default spacing scale is already 0.25rem steps; use it rather than arbitrary `p-[13px]` values.
|
|
19
|
+
|
|
20
|
+
## Type scale (ratio 1.25, base 16px)
|
|
21
|
+
|
|
22
|
+
| Token | rem | px | Use |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| `--text-xs` | 0.75 | 12 | captions, badges (avoid for body) |
|
|
25
|
+
| `--text-sm` | 0.875 | 14 | secondary text, dense tables |
|
|
26
|
+
| `--text-base` | 1 | 16 | body |
|
|
27
|
+
| `--text-lg` | 1.25 | 20 | card titles |
|
|
28
|
+
| `--text-xl` | 1.563 | 25 | page titles |
|
|
29
|
+
| `--text-2xl` | 1.953 | 31 | hero / display |
|
|
30
|
+
|
|
31
|
+
Use `rem` so user font-size settings apply. For fluid headings, `clamp(1.5rem, 1rem + 2vw, 2.5rem)`; keep body text fixed.
|
|
32
|
+
|
|
33
|
+
Weights: 400 body, 500-600 labels and buttons, 600-700 headings. Two or three weights per family.
|
|
34
|
+
|
|
35
|
+
## Colour: palette to semantic layer
|
|
36
|
+
|
|
37
|
+
```css
|
|
38
|
+
:root {
|
|
39
|
+
/* palette */
|
|
40
|
+
--gray-50: #f8f8f7; --gray-100: #efeeec; --gray-200: #dddbd7; --gray-500: #79756e;
|
|
41
|
+
--gray-700: #45423d; --gray-900: #1d1c1a;
|
|
42
|
+
--teal-600: #0d7c74; --teal-700: #0a645e;
|
|
43
|
+
--red-600: #c4321f; --amber-600: #b26b00; --green-600: #2f7d32;
|
|
44
|
+
|
|
45
|
+
/* semantic */
|
|
46
|
+
--color-bg: var(--gray-50);
|
|
47
|
+
--color-surface: #fff;
|
|
48
|
+
--color-text: var(--gray-900);
|
|
49
|
+
--color-text-muted: var(--gray-700);
|
|
50
|
+
--color-border: var(--gray-200);
|
|
51
|
+
--color-accent: var(--teal-600);
|
|
52
|
+
--color-accent-hover: var(--teal-700);
|
|
53
|
+
--color-danger: var(--red-600);
|
|
54
|
+
}
|
|
55
|
+
@media (prefers-color-scheme: dark) {
|
|
56
|
+
:root { --color-bg: var(--gray-900); --color-surface: #262522; --color-text: var(--gray-50); /* ... */ }
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
These hex values are illustrative; check each text/background pair with a contrast checker before use. `oklch()` makes even lightness steps easier to build when the project's browser targets support it.
|
|
61
|
+
|
|
62
|
+
Dark mode is not inverted light mode: lower the surface contrast, desaturate the accent slightly, and use lighter surfaces (not shadows) for elevation.
|
|
63
|
+
|
|
64
|
+
## Radius, shadow, z-index
|
|
65
|
+
|
|
66
|
+
- Radius: `--radius-sm` 4px (inputs, tags), `--radius-md` 8px (buttons, cards), `--radius-full` 9999px (pills, avatars). Nested radius = outer radius minus padding.
|
|
67
|
+
- Shadow: `--shadow-sm` for raised controls, `--shadow-lg` for popovers and dialogs only.
|
|
68
|
+
- Z-index: named layers (`--z-dropdown: 10; --z-sticky: 20; --z-overlay: 30; --z-modal: 40; --z-toast: 50`), never `9999`.
|