forgetrail 0.3.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/CONTINUATION_PROMPT.md +42 -0
- package/INITIAL_PROMPT.md +214 -0
- package/LICENSE +201 -0
- package/README.md +198 -0
- package/TRACKING_SCHEMA.md +162 -0
- package/TRY_FORGETRAIL.md +78 -0
- package/WORKFLOW.md +921 -0
- package/content/AGENT_INTEGRATION_claude.md +38 -0
- package/content/AGENT_INTEGRATION_cursor.md +53 -0
- package/content/AGENT_INTEGRATION_generic.md +50 -0
- package/content/AGENT_INTEGRATION_grok.md +82 -0
- package/content/DEV_AUTOMATION_SCRIPTS.md +103 -0
- package/content/FORGETRAIL_LITE.md +1202 -0
- package/content/FORGETRAIL_LITE_UPDATES.md +13 -0
- package/content/FORGETRAIL_PROGRESS.md +26 -0
- package/content/GENESIS_SPEC_PROMPT.md +70 -0
- package/content/GENESIS_STUB.md +18 -0
- package/content/GREENFIELD_INTAKE.md +76 -0
- package/content/KICKOFF_WITHOUT_MCP.md +16 -0
- package/content/LITE_WORKFLOW_TRACKING.json +42 -0
- package/content/NEW_PROJECT_BOOTSTRAP.md +180 -0
- package/content/ONE_CLICK_DEV_SETUP.md +142 -0
- package/content/PLAN_MODE_PATTERNS.md +43 -0
- package/content/POCKETBASE_SCHEMA_SCRIPT.md +52 -0
- package/content/POST_BOOTSTRAP_USER_MESSAGE.md +51 -0
- package/content/SCAFFOLD_INSTALL.json +110 -0
- package/content/SESSION_RESUME_MCP.md +35 -0
- package/content/SYSTEM_HEALTH_CHECKS.md +131 -0
- package/content/USER_REPLY_FORMAT.md +41 -0
- package/content/cursor-rules/forgetrail-lessons-gate.mdc +30 -0
- package/content/cursor-rules/forgetrail-lessons-mcp.mdc +24 -0
- package/content/cursor-rules/forgetrail-no-trailer.mdc +49 -0
- package/content/cursor-rules/forgetrail-phase-status.mdc +61 -0
- package/content/cursor-rules/forgetrail-updates-log.mdc +30 -0
- package/content/cursor-rules/spec-completion.mdc +33 -0
- package/content/cursor-rules/specs-and-todo.mdc +26 -0
- package/content/cursor-rules/url-host-matching.mdc +31 -0
- package/content/cursor-rules/us-english.mdc +9 -0
- package/content/cursor-rules/user-facing-content.mdc +31 -0
- package/content/examples/GENESIS_SAMPLE_mars-habitat-roster.md +154 -0
- package/content/forgetrail-workspace-README.md +43 -0
- package/content/scripts/forgetrail-dev-launcher.mjs +208 -0
- package/content/scripts/forgetrail-env.mjs +51 -0
- package/content/scripts/setup-ollama.mjs +207 -0
- package/content/scripts/setup-pocketbase.mjs +133 -0
- package/content/scripts/test-ollama.mjs +71 -0
- package/content/scripts/test-pocketbase.mjs +110 -0
- package/content/skills/forgetrail/SKILL.md +92 -0
- package/docs/AUTOMATED_TESTING.md +269 -0
- package/docs/BLACK_HAT_REPORT.md +143 -0
- package/docs/BRAND_AND_PRODUCT.md +381 -0
- package/docs/BUGS.md +62 -0
- package/docs/BUSINESS_PLAN.md +256 -0
- package/docs/CODE_QUALITY.md +170 -0
- package/docs/CONTEXT_PROMPT.md +357 -0
- package/docs/DEPLOYMENT.md +181 -0
- package/docs/DESIGN_SYSTEM.md +414 -0
- package/docs/DEV_ESTIMATE.md +127 -0
- package/docs/FORGETRAIL_RENAME.md +193 -0
- package/docs/IDEAS.md +19 -0
- package/docs/MARKETING_GROWTH.md +301 -0
- package/docs/NAMING_EXPLORATION.md +406 -0
- package/docs/NPM.md +98 -0
- package/docs/PHASE_1_BRIEF.md +161 -0
- package/docs/README.md +75 -0
- package/docs/SPEC_FEATURE_TEMPLATE.md +155 -0
- package/docs/SPEC_UI_CHROME_NAV_TEMPLATE.md +48 -0
- package/docs/TECHNICAL_REFERENCE.md +836 -0
- package/docs/TEST_PLAN.md +467 -0
- package/docs/TODO.md +125 -0
- package/package.json +68 -0
- package/prompts/black-hat-audit.md +235 -0
- package/prompts/brand-copy-edit-pass.md +55 -0
- package/prompts/cialdini-marketing-audit.md +141 -0
- package/prompts/competitor-deep-dive.md +201 -0
- package/prompts/docs-alignment-audit.md +108 -0
- package/prompts/engineering-skill-library.md +75 -0
- package/prompts/landing-page-rewrite.md +63 -0
- package/prompts/microcopy-centralization.md +128 -0
- package/prompts/panel-usability-audit.md +197 -0
- package/prompts/personal-beta-outreach.md +195 -0
- package/prompts/pre-launch-audit.md +148 -0
- package/prompts/product-feedback-to-spec.md +76 -0
- package/prompts/propagate-to-forgetrail.md +315 -0
- package/prompts/user-facing-content-sync-audit.md +200 -0
- package/prompts/ux-cohesion-audit.md +165 -0
- package/scripts/ensure-lease.mjs +23 -0
- package/scripts/forgetrail-cli.mjs +85 -0
- package/scripts/install-forgetrail-lite.mjs +11 -0
- package/scripts/install-forgetrail.mjs +11 -0
- package/scripts/install-lib.mjs +115 -0
- package/scripts/install.mjs +163 -0
- package/scripts/link-global.mjs +100 -0
- package/scripts/mcp-lib.mjs +177 -0
- package/scripts/mcp-status.mjs +155 -0
- package/scripts/publish-gate.mjs +110 -0
- package/scripts/rename-to-forgetrail.mjs +273 -0
- package/workflow_tracking.json +124 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# [App Name]: Deployment & Operations
|
|
2
|
+
|
|
3
|
+
_Current status, what needs to change before launch, go-live checklist, implementation roadmap, monitoring, and cost estimates._
|
|
4
|
+
|
|
5
|
+
_For technical feature reference, see [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md). For business model and pricing, see [BUSINESS_PLAN.md](BUSINESS_PLAN.md). For code quality findings, see [CODE_QUALITY.md](CODE_QUALITY.md). For security vulnerabilities, see [BLACK_HAT_REPORT.md](BLACK_HAT_REPORT.md)._
|
|
6
|
+
|
|
7
|
+
_Instructions: Start this document when you begin thinking about production (Phase 7: Hardening). It's both a status tracker and an operational runbook. The go-live checklist should be specific enough that you (or Claude) can execute it step-by-step without interpretation._
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Current Status
|
|
12
|
+
|
|
13
|
+
### What's Done
|
|
14
|
+
|
|
15
|
+
- [ ] [Capability]: [Brief status note]
|
|
16
|
+
- [ ] [Capability]: [Brief status note]
|
|
17
|
+
|
|
18
|
+
### What's NOT Done
|
|
19
|
+
|
|
20
|
+
- [ ] [Capability]: [What's needed, estimated effort]
|
|
21
|
+
- [ ] [Capability]: [What's needed, estimated effort]
|
|
22
|
+
|
|
23
|
+
## What Needs to Change Before Launch
|
|
24
|
+
|
|
25
|
+
_Organized by category. Reference specific CODE_QUALITY.md findings where applicable._
|
|
26
|
+
|
|
27
|
+
### Security (Blockers)
|
|
28
|
+
|
|
29
|
+
> 💡 **Lesson learned:** These four categories consistently surfaced as launch blockers:
|
|
30
|
+
> 1. Input sanitization on database queries (filter injection, SQL injection)
|
|
31
|
+
> 2. Cookie/session security (httpOnly, secure, sameSite flags)
|
|
32
|
+
> 3. Rate limiting on expensive endpoints (LLM calls, scraping)
|
|
33
|
+
> 4. Prompt injection defense (user input flowing into LLM prompts)
|
|
34
|
+
>
|
|
35
|
+
> Additional hardening from Exec Foundry Session 9:
|
|
36
|
+
> 5. Centralized input validation with Zod on ALL API routes (see TECHNICAL_REFERENCE.md > Input Validation)
|
|
37
|
+
> 6. IDOR checks on every data-access route (verify record belongs to requesting user)
|
|
38
|
+
> 7. Promo code redemption race conditions (atomic check-and-increment)
|
|
39
|
+
> 8. Delegation access level enforcement on mutation routes
|
|
40
|
+
|
|
41
|
+
- [ ] [Issue]: [Current state → Required state. Reference CODE_QUALITY finding if applicable.]
|
|
42
|
+
|
|
43
|
+
### Auth
|
|
44
|
+
|
|
45
|
+
- [ ] [Issue]: [Current state → Required state]
|
|
46
|
+
|
|
47
|
+
### Payments
|
|
48
|
+
|
|
49
|
+
- [ ] [Issue]: [Current state → Required state]
|
|
50
|
+
|
|
51
|
+
### Known Bugs (Blockers)
|
|
52
|
+
|
|
53
|
+
- [ ] [Bug]: [Impact and fix needed]
|
|
54
|
+
|
|
55
|
+
### Non-Blocking but Important
|
|
56
|
+
|
|
57
|
+
- [ ] [Issue]: [Why it matters, when to address]
|
|
58
|
+
|
|
59
|
+
## Infrastructure
|
|
60
|
+
|
|
61
|
+
### Target Setup
|
|
62
|
+
|
|
63
|
+
| Component | Choice | Spec | Monthly Cost |
|
|
64
|
+
| ---------- | ---------------------------------- | ----------------------- | ------------ |
|
|
65
|
+
| Hosting | [e.g., DigitalOcean] | [e.g., 1 vCPU, 2GB RAM] | $[X] |
|
|
66
|
+
| Database | [e.g., PocketBase on same droplet] | [included] | $0 |
|
|
67
|
+
| Domain | [e.g., Cloudflare] | [domain name] | $[X]/yr |
|
|
68
|
+
| SSL | [e.g., Caddy auto-HTTPS] | [included] | $0 |
|
|
69
|
+
| CI/CD | [e.g., GitHub Actions] | [free tier] | $0 |
|
|
70
|
+
| Monitoring | [e.g., UptimeRobot] | [free tier] | $0 |
|
|
71
|
+
|
|
72
|
+
### DNS delegation order (registrar + Cloudflare + origin)
|
|
73
|
+
|
|
74
|
+
Typical stack: domain at **Namecheap** (or any registrar), **Cloudflare** for DNS and edge TLS, **DigitalOcean** (or similar) for the origin.
|
|
75
|
+
|
|
76
|
+
1. **Cloudflare first:** Add the domain as a **zone** in Cloudflare. Cloudflare assigns **two nameservers** (e.g. `*.ns.cloudflare.com`). You need those before changing the registrar.
|
|
77
|
+
2. **In Cloudflare DNS:** Point `A`/`CNAME` records at the origin server’s IP or hostname (pre-fill before delegation so cutover is clean).
|
|
78
|
+
3. **Registrar (e.g. Namecheap):** Set **Custom DNS** / nameservers to **only** Cloudflare’s two nameservers for that zone. Do **not** switch nameservers at the registrar until the Cloudflare zone exists and you have copied the correct pair.
|
|
79
|
+
4. Wait until the zone shows **Active** in Cloudflare; then **SSL/TLS** (e.g. Full / Full strict) and redirects behave as expected.
|
|
80
|
+
|
|
81
|
+
**Cloudflare plan:** **Free** is enough for most small production apps (Universal SSL, DNS, CDN, basic protection). Use **Pro** if you need advanced WAF rules, more redirect rules, or higher support priority.
|
|
82
|
+
|
|
83
|
+
> 💡 **Lesson learned:** **Outbound transactional email** (app-owned, not only BaaS password reset): verify the **sending domain** with your mail provider and add the records they require (**SPF**, **DKIM**, often **DMARC**) in DNS before go-live. Staging can use a subdomain or provider test mode; production deliverability failures often show up only as silent drops or spam folders.
|
|
84
|
+
|
|
85
|
+
> 💡 **Lesson learned:** If your app uses Playwright (headless browser), budget 2x the RAM. A 1GB droplet can't handle Playwright + app server concurrently. Consider: move scraping to async worker, upgrade to 2GB, or drop Playwright for lighter alternatives.
|
|
86
|
+
|
|
87
|
+
### Environment Variables
|
|
88
|
+
|
|
89
|
+
| Variable | Purpose | Where Set |
|
|
90
|
+
| ---------- | --------- | -------------------------------------- |
|
|
91
|
+
| [VAR_NAME] | [purpose] | [.env / hosting provider / CI secrets] |
|
|
92
|
+
|
|
93
|
+
> 💡 **Lesson learned:** **Keep Node.js and package-manager versions aligned across `package.json`, CI, and production.** Declare `engines.node` (and `packageManager` for pnpm) to match what GitHub Actions, Docker, and the host actually run. Updating CI’s Node version without updating `engines` (or vice versa) produces “works on deploy” / “fails locally” drift and subtle dependency resolution differences. Treat **workflow `node-version`**, **`engines`**, and the **runtime image** as one triplet to bump together.
|
|
94
|
+
|
|
95
|
+
> 💡 **Lesson learned:** **Rolling restarts and reverse proxies:** A single upstream socket that goes down for process restart can surface **502/503** to users for a few seconds. Prefer **multiple** app instances or staggered slots behind the proxy with **health-checked** upstreams; configure brief **retry** or drain behavior where safe. **Why:** Deploy-time blips generate more noise than many logic bugs.
|
|
96
|
+
|
|
97
|
+
> 💡 **Lesson learned:** **`systemd` stop timeout vs deploy wall-clock:** Raising **`TimeoutStopSec`** (or equivalent) so long requests can **drain gracefully** on SIGTERM is often correct for LLM or upload-heavy apps — but **`systemctl stop`** on the old instance waits **up to** that budget before SIGKILL. If the process does not exit promptly on SIGTERM, **each** deploy’s SSH/CI step can grow by a large fraction of that window. **Why:** Blue-green or rolling scripts typically **stop** the superseded unit after traffic moves; a longer cap fixes mid-request kills but can **double** apparent pipeline duration if shutdown is slow. **Mitigations:** implement a **short** graceful shutdown in the app (close listener, bound wait on in-flight work), pick a **middle-ground** timeout, and **split timings** in CI (build vs deploy) so regressions point at compile vs systemd stop.
|
|
98
|
+
|
|
99
|
+
## Go-Live Checklist
|
|
100
|
+
|
|
101
|
+
_Prerequisites and numbered steps. Specific enough to execute without interpretation._
|
|
102
|
+
|
|
103
|
+
### Prerequisites
|
|
104
|
+
|
|
105
|
+
- [ ] All Critical findings in CODE_QUALITY.md are fixed
|
|
106
|
+
- [ ] All Critical/High findings in BLACK_HAT_REPORT.md are fixed (P0 tier in TODO.md)
|
|
107
|
+
- [ ] Auth flow tested end to end
|
|
108
|
+
- [ ] Payment flow tested end to end (if applicable)
|
|
109
|
+
- [ ] Domain and DNS configured
|
|
110
|
+
- [ ] SSL certificate provisioned
|
|
111
|
+
- [ ] Environment variables set in production
|
|
112
|
+
- [ ] Database backed up (if migrating existing data)
|
|
113
|
+
|
|
114
|
+
### Deploy Steps
|
|
115
|
+
|
|
116
|
+
1. [ ] [Step]: [Exact command or action]
|
|
117
|
+
2. [ ] [Step]: [Exact command or action]
|
|
118
|
+
3. [ ] [Step]: [Exact command or action]
|
|
119
|
+
|
|
120
|
+
### Post-Deploy Verification
|
|
121
|
+
|
|
122
|
+
1. [ ] [Check]: [What to verify and how]
|
|
123
|
+
2. [ ] [Check]: [What to verify and how]
|
|
124
|
+
|
|
125
|
+
## Implementation Roadmap
|
|
126
|
+
|
|
127
|
+
_Break remaining work into phases with dependencies and time estimates._
|
|
128
|
+
|
|
129
|
+
### Phase 1: [Name] ([estimated days])
|
|
130
|
+
|
|
131
|
+
_Dependencies: [what must be done first]_
|
|
132
|
+
|
|
133
|
+
- [ ] [Task]
|
|
134
|
+
- [ ] [Task]
|
|
135
|
+
|
|
136
|
+
### Phase 2: [Name] ([estimated days])
|
|
137
|
+
|
|
138
|
+
_Dependencies: Phase 1_
|
|
139
|
+
|
|
140
|
+
- [ ] [Task]
|
|
141
|
+
- [ ] [Task]
|
|
142
|
+
|
|
143
|
+
## Monitoring & Alerting
|
|
144
|
+
|
|
145
|
+
### Uptime
|
|
146
|
+
|
|
147
|
+
- [Service]: [URL to monitor, expected response]
|
|
148
|
+
|
|
149
|
+
### Error Alerting
|
|
150
|
+
|
|
151
|
+
- [Method]: [e.g., PocketBase logs → email on 5xx, Sentry for frontend errors]
|
|
152
|
+
|
|
153
|
+
### Health Checks
|
|
154
|
+
|
|
155
|
+
- [Endpoint]: [What it checks, expected response]
|
|
156
|
+
|
|
157
|
+
### Backup Strategy
|
|
158
|
+
|
|
159
|
+
- [What]: [How often, where stored, how to restore]
|
|
160
|
+
|
|
161
|
+
## Quick Reference
|
|
162
|
+
|
|
163
|
+
| Resource | URL/Path |
|
|
164
|
+
| ----------------- | -------------------- |
|
|
165
|
+
| Production URL | [https://...] |
|
|
166
|
+
| Staging URL | [if applicable] |
|
|
167
|
+
| Admin panel | [URL] |
|
|
168
|
+
| CI/CD | [GitHub Actions URL] |
|
|
169
|
+
| Hosting dashboard | [URL] |
|
|
170
|
+
| Domain registrar | [URL] |
|
|
171
|
+
| Error tracking | [URL] |
|
|
172
|
+
|
|
173
|
+
## Cost Summary
|
|
174
|
+
|
|
175
|
+
| Item | Monthly | Annual |
|
|
176
|
+
| ------------------------ | --------------- | -------- |
|
|
177
|
+
| Hosting | $[X] | $[X] |
|
|
178
|
+
| API costs (at [N] users) | $[X] | $[X] |
|
|
179
|
+
| Domain | $[X/12] | $[X] |
|
|
180
|
+
| Payment processing | [X]% of revenue | varies |
|
|
181
|
+
| **Total** | **$[X]** | **$[X]** |
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# [App Name]: Design System & Visual Philosophy
|
|
2
|
+
|
|
3
|
+
_For brand voice and copy guidelines, see [BRAND_AND_PRODUCT.md](BRAND_AND_PRODUCT.md). For technical implementation, see [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md)._
|
|
4
|
+
|
|
5
|
+
_Instructions: Create this document during Phase 6 (Strategic Review) or whenever your visual language has solidified enough to codify. It serves two purposes: (1) guidance for Claude when building new components, and (2) a reference to prevent visual drift across sessions. Update when the design matures, but it should stabilize relatively early._
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Design Sensibility
|
|
10
|
+
|
|
11
|
+
_3-5 sentences that describe the emotional register of the interface. What should it FEEL like to use? What should it NOT feel like?_
|
|
12
|
+
|
|
13
|
+
[Describe the intended emotional quality. Reference analogies if helpful — "like a well-appointed workspace" or "like a professional's private notebook."]
|
|
14
|
+
|
|
15
|
+
### Guiding Principles
|
|
16
|
+
|
|
17
|
+
1. **[Principle]:** [How it manifests in visual decisions]
|
|
18
|
+
2. **[Principle]:** [How it manifests in visual decisions]
|
|
19
|
+
3. **[Principle]:** [How it manifests in visual decisions]
|
|
20
|
+
|
|
21
|
+
> 📝 **Example:**
|
|
22
|
+
> 1. Earned restraint over gratuitous polish — every visual flourish must serve a purpose (signaling status, guiding attention, reinforcing hierarchy)
|
|
23
|
+
> 2. Felt, not seen — the best details register subconsciously. Shadows, tints, micro-animations. Users should feel "this is polished" without pinpointing why.
|
|
24
|
+
> 3. Warmth over sterility — warm neutrals and soft shadows prevent the clinical feel of most SaaS
|
|
25
|
+
> 4. Accent as punctuation — the brand color appears sparingly, like a signature pen on a handwritten note. Overuse dilutes it.
|
|
26
|
+
> 5. Density without clutter — the audience is information-literate. Organize density with clear hierarchy, don't hide it behind progressive disclosure.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Color System
|
|
31
|
+
|
|
32
|
+
### Light Theme
|
|
33
|
+
|
|
34
|
+
| Token | Value | Role |
|
|
35
|
+
| ---------------------- | ------- | --------------------------- |
|
|
36
|
+
| `--app-bg` | [color] | Page background |
|
|
37
|
+
| `--app-surface` | [color] | Cards, panels, raised areas |
|
|
38
|
+
| `--app-surface-hover` | [color] | Interactive hover states |
|
|
39
|
+
| `--app-surface-raised` | [color] | Highest elevation (modals) |
|
|
40
|
+
| `--app-border` | [color] | Primary borders |
|
|
41
|
+
| `--app-text` | [color] | Primary text |
|
|
42
|
+
| `--app-text-muted` | [color] | Secondary text |
|
|
43
|
+
| `--app-accent` | [color] | Brand accent |
|
|
44
|
+
| `--app-accent-hover` | [color] | Accent interaction state |
|
|
45
|
+
|
|
46
|
+
### Dark Theme
|
|
47
|
+
|
|
48
|
+
_Same tokens, different values. Note the deltas, not just the colors._
|
|
49
|
+
|
|
50
|
+
| Token | Value | Role |
|
|
51
|
+
| --------------- | ------- | ------------------------------------------------------ |
|
|
52
|
+
| `--app-bg` | [color] | [e.g., deep navy, not black] |
|
|
53
|
+
| `--app-surface` | [color] | |
|
|
54
|
+
| `--app-accent` | [color] | [e.g., lighter variant of accent for dark bg contrast] |
|
|
55
|
+
|
|
56
|
+
> 💡 **Lesson learned:** Avoid pure white (#ffffff) or pure black (#000000) for backgrounds. Warm palettes (parchment light, deep navy dark) create emotional warmth that users register subconsciously. The difference between "generic SaaS" and "this feels premium" is often just 10-15 degrees of hue shift in the neutrals.
|
|
57
|
+
>
|
|
58
|
+
> Also expose the accent color as an `--app-accent-rgb` variable (e.g., `55, 48, 163`) so it can be used in `rgba()` functions throughout the CSS for opacity-controlled accent washes without hardcoding the color value everywhere.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Shadow System
|
|
63
|
+
|
|
64
|
+
| Token | Purpose | Light Theme Value |
|
|
65
|
+
| --------------------- | ------------------- | ----------------- |
|
|
66
|
+
| `--shadow-sm` | Navbar, subtle lift | [value] |
|
|
67
|
+
| `--shadow-md` | Moderate cards | [value] |
|
|
68
|
+
| `--shadow-lg` | Dropdowns, overlays | [value] |
|
|
69
|
+
| `--shadow-xl` | Tooltips, popovers | [value] |
|
|
70
|
+
| `--shadow-card` | Item cards at rest | [value] |
|
|
71
|
+
| `--shadow-card-hover` | Cards on hover | [value] |
|
|
72
|
+
| `--shadow-panel` | Slide-out panels | [value] |
|
|
73
|
+
|
|
74
|
+
> 💡 **Lesson learned:** Card hover shadows and panel shadows should carry a barely perceptible accent tint (rgba(accent, 0.04) outer glow). Combined with gradient backgrounds, this makes the brand color feel ambient — it's everywhere but nowhere specific.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Accent Hierarchy
|
|
79
|
+
|
|
80
|
+
_Define three intensity tiers for the brand accent color. This prevents both overuse and underuse._
|
|
81
|
+
|
|
82
|
+
### Strong (clearly visible)
|
|
83
|
+
|
|
84
|
+
- Primary CTA buttons
|
|
85
|
+
- Active tab underlines
|
|
86
|
+
- Status badges and active filter pills
|
|
87
|
+
|
|
88
|
+
### Medium (noticeable on attention)
|
|
89
|
+
|
|
90
|
+
- Icon containers (`bg-accent/10 text-accent`)
|
|
91
|
+
- Card/modal top-borders (`border-t-2 border-t-accent/40-50`)
|
|
92
|
+
- Panel left-edge borders on emphasis elements
|
|
93
|
+
|
|
94
|
+
> 💡 **Lesson learned:** An accent top-border on a component flush with the browser viewport edge (e.g. a full-height side panel anchored to `top-0`) is effectively invisible — the thin line blends into the window chrome. Move the accent to a bottom-border on headers or a visible inset edge instead. Reserve top-borders for cards and modals that have clear visual space above them.
|
|
95
|
+
|
|
96
|
+
### Subtle (felt, not seen)
|
|
97
|
+
|
|
98
|
+
- Card hover shadow accent tint (`rgba(accent, 0.04)`)
|
|
99
|
+
- Panel left-edge shadow glow
|
|
100
|
+
- Active tab background wash (`bg-accent/[0.04]`)
|
|
101
|
+
- Grid/pattern backgrounds at 2-3% opacity
|
|
102
|
+
- **Gradient backgrounds on surfaces** — layered `radial-gradient()` over `var(--color-surface)` at 3-7% opacity
|
|
103
|
+
|
|
104
|
+
> 💡 **Lesson learned:** The subtle tier is the most important. It's what makes the difference between "clean but generic" and "this feels intentional." Define reusable CSS classes: `.modal-gradient` (radial-gradient from accent at top-left, over surface), `.panel-gradient` (radial-gradient from left-edge + linear-gradient top-fade, over surface). Apply to ALL modals and panels uniformly. Use slightly higher opacity in dark mode (5-7%) than light mode (3-5%) because dark backgrounds absorb more of the color. Keep the Tailwind `bg-surface` class alongside the gradient class as a fallback.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Layout Patterns
|
|
109
|
+
|
|
110
|
+
### Slide-Out Panels — Width Tiers
|
|
111
|
+
|
|
112
|
+
_Define standard panel widths for consistency. Not every panel needs to be unique._
|
|
113
|
+
|
|
114
|
+
| Tier | Width | Use Cases |
|
|
115
|
+
| -------- | -------------------- | ---------------------------------- |
|
|
116
|
+
| Standard | `max-w-2xl` (672px) | Profile, settings, content editing |
|
|
117
|
+
| Medium | `max-w-3xl` (768px) | Lists, help, moderate detail |
|
|
118
|
+
| Large | `max-w-5xl` (1024px) | Detail views, dashboards, admin |
|
|
119
|
+
|
|
120
|
+
All panels share: `fixed right-0 top-0 bottom-0`, entrance animation, `bg-surface [panel-gradient]`, `border-l`, backdrop with click-outside.
|
|
121
|
+
|
|
122
|
+
### Panel interior: compact hero band (workspace panels)
|
|
123
|
+
|
|
124
|
+
_Use inside slide-out panels when the top of the surface is **list + search + filters + primary content** — not a rich “default selection” or preview row._
|
|
125
|
+
|
|
126
|
+
Dense workspace panels (e.g. a **[Resource] library**, **[Entity] bank**, or categorized **list + master–detail** layout) often need a short blurb and status chips but **not** a large icon circle: the panel header already carries the icon and title. Prefer a **compact hero** — still `card-elevated card-elevated-no-hover` with `border-t-2 border-t-accent/35`, but:
|
|
127
|
+
|
|
128
|
+
- **Inline icon** — `w-5 h-5 text-accent shrink-0` instead of a ~5rem circle and sub-label.
|
|
129
|
+
- **One row** — blurb (`text-xs text-text-muted leading-snug`, `min-w-0 flex-1`) plus optional **status chips** (`flex flex-wrap gap-2`) and **kebab / overflow actions** top-right (`shrink-0 self-start`); keep overflow actions out of the panel header.
|
|
130
|
+
- **Search and filter chips inside the same card** — full-width search under that row; horizontal filter chips below when needed (reuse the same pill/chip patterns as elsewhere in the product).
|
|
131
|
+
- **Tighter padding** — inner `px-4 py-2 space-y-2`; outer band around the card `shrink-0 px-5 pt-2 pb-2 space-y-2` (vs looser padding when using a **full** hero with a large circle and extra stacked rows).
|
|
132
|
+
|
|
133
|
+
**When to use a full hero instead** — The panel needs a strong visual anchor for first-time orientation (e.g. current default selection, readiness state, live preview). Use **compact** when the user should reach the list or editor quickly.
|
|
134
|
+
|
|
135
|
+
> 💡 **Lesson learned:** Merging search, filters, and the blurb into one **compact hero** card removes dead vertical gaps between separate blocks. Pick **compact** for workspace panels where the panel header already identifies the surface; document both patterns in your living design spec so new panels default to the right variant.
|
|
136
|
+
|
|
137
|
+
> 💡 **Lesson learned:** **Run a systematic vertical density pass** across all panels and tabs once the feature set stabilizes. Common wins: remove redundant section headers that duplicate the tab label, collapse instructional text into the textarea placeholder or a tooltip, tighten `gap-*` and `py-*` from generous early-development spacing (e.g., `gap-6` → `gap-3`, `py-6` → `py-3`), and eliminate per-section intro paragraphs when the panel header or tab name already communicates the purpose. A canonical spec (e.g., `specs/canonical/panel-vertical-density.md`) with shared CSS class tokens and before/after spacing conventions keeps the pass consistent across panels. **Why:** Every unnecessary line of chrome above the primary input area costs the user a scroll; density is especially critical on slide-over panels where vertical space is limited.
|
|
138
|
+
|
|
139
|
+
### Panel navigation model (side-tab rail)
|
|
140
|
+
|
|
141
|
+
_Use when a slide-out panel has **≥ 3 co-equal sections** (settings groups, feature areas, workflow stages) that today live as a top tab strip, nested tab bars, or a long accordion._
|
|
142
|
+
|
|
143
|
+
Once a panel grows past two modes, a **section rail** reads calmer than stacked cards or a crowded tab strip. The canonical shape has four rules:
|
|
144
|
+
|
|
145
|
+
1. **Persistent header card.** The panel's primary object (the file, the record, the summary) renders **once at the top** and stays visible across every section — it is not a section you can navigate away from.
|
|
146
|
+
2. **A section rail, not stacked cards.** Sections are a **vertical rail on wide screens** (`lg:` and up, `lg:sticky lg:top-0`) and a **horizontal scrollable pill strip on narrow screens**. One click reaches any section.
|
|
147
|
+
3. **One scroll.** The panel body is the **only** `overflow-y-auto` region; content panes do not add a second nested scrollbar in the same column (see the single-scroll anti-pattern in `CONTEXT_PROMPT.md`).
|
|
148
|
+
4. **One content pane at a time**, chosen by a single `*Section`/`activeTab` state with a reset effect when the panel/tab closes.
|
|
149
|
+
|
|
150
|
+
> 🔧 **Guidance:** Adopt a rail at **≥ 3 sections**; panels with **≤ 2 modes** keep a plain top tab strip (a rail adds chrome without payoff). **Exception — master-detail panels** (an entity list you pick from: contacts, stories, saved records) keep their own list rail and its independent list scroll; they borrow only rules 1 and 3 (persistent header, no nested scroll **inside the detail pane**). Extract two shared primitives so the model does not re-multiply chrome: a **`PanelShell`** (backdrop, fixed-right column, entrance animation, header slot, optional non-scrolling `belowHeader` slot for tab strips/search, single scroll body, built-in width toggle) and a **`PanelSideNav`** (responsive rail driven by a typed `{ id, icon, label, statusLabel?, disabled? }[]`). Record the model in a living design/spec doc so new panels default to it. **Why:** Every panel that reinvents its own interior wayfinding is a "feels like N stitched-together apps" cohesion tell; a shared shell + rail makes wayfinding identical everywhere and removes duplicated backdrop/scroll markup.
|
|
151
|
+
|
|
152
|
+
### Modals
|
|
153
|
+
|
|
154
|
+
- Centered with backdrop
|
|
155
|
+
- Entrance animation (scale-up, 200ms)
|
|
156
|
+
- Accent top-border (`border-t-2 border-t-accent/50`)
|
|
157
|
+
- `bg-surface [modal-gradient]`
|
|
158
|
+
- `shadow-2xl`
|
|
159
|
+
- For modals with many inputs: use `flex-col md:grid md:grid-cols-2 gap-4` for responsive two-column layout at `md:` breakpoint
|
|
160
|
+
|
|
161
|
+
> 💡 **Lesson learned:** Modals with 4+ input fields benefit from a two-column layout at desktop widths. Put primary inputs (the "what") in the left column, supplementary context (the "extra") in the right. Merge mode toggles into the header row as compact segmented pills to save vertical space. The container uses `max-w-lg md:max-w-4xl` for responsive breakpoint switching. Mobile falls back to single-column automatically.
|
|
162
|
+
|
|
163
|
+
> 💡 **Lesson learned:** **Avoid cloning state variables and form logic between a modal dialog wrapper and an inline tab form.** If a creation/editing form is exposed both as a standalone modal (e.g., an "Add Connection" modal from a dashboard) and as an inline tab (e.g., "Add Connection" within a detail tab), do not duplicate the state and controllers. Extract the entire form (input bindings, search, clipboard parsing, and validations) into a reusable form component (`[Entity]Form.svelte`). The modal (`[Entity]Modal.svelte`) then acts as a pure modal layout boundary wrapping `<[Entity]Form>`, keeping the modal script extremely lean and making fields easily maintainable in one place.
|
|
164
|
+
|
|
165
|
+
> 💡 **Lesson learned:** **Extract inline add/create forms into standalone modals when the form grows beyond 2-3 fields or appears in multiple contexts.** Inline forms inside tabs or panels compete for scroll space with the content they create, and they can't be reused from other entry points (quick-add buttons, onboarding, bulk flows). Extract to a dedicated modal component; give the modal a focused layout (icon + title header, consolidated field rows for density, mode toggles as compact pills). The parent tab becomes a trigger + list, gaining vertical real estate. **When to keep inline:** Single-field "quick add" (e.g., paste a URL) where the modal overhead would slow the user down.
|
|
166
|
+
|
|
167
|
+
### Navbar
|
|
168
|
+
|
|
169
|
+
- Sticky `top-0`, `z-30` (use `z-50` on landing/marketing pages where the nav must sit above hero content)
|
|
170
|
+
- `backdrop-blur-sm` (or `backdrop-blur-md` for marketing pages) with slight transparency (`bg-bg/80`)
|
|
171
|
+
- Subtle accent shadow below
|
|
172
|
+
- On marketing/landing pages, pull the `<nav>` out of the hero `<section>` into its own top-level element so sticky positioning works correctly (a nav inside a hero section scrolls away with it)
|
|
173
|
+
- The nav logo should be a clickable link (e.g., `<a href="/start">`) — not a static image
|
|
174
|
+
|
|
175
|
+
> 💡 **Lesson learned:** If the bar **stays visible** while the page scrolls **and** its **height changes** (smaller logo, tighter padding on scroll), **`position: sticky` alone is often the wrong tool**: parent overflow and layout can prevent a reliable “pinned” feel. Prefer **`position: fixed`** on the bar plus a **non-interactive spacer** in normal document flow whose height **matches** the bar’s expanded vs compact size (transition both together). **Why:** A shrinking bar changes layout height; that nudges scroll position and can flip a “scrolled” threshold back and forth → visible **jitter**. Pair the spacer with **hysteresis** on scroll (switch to compact only after `scrollY` passes a higher value, switch back only below a lower value) so the state doesn’t oscillate at the boundary.
|
|
176
|
+
|
|
177
|
+
### Full-height main shell (app chrome)
|
|
178
|
+
|
|
179
|
+
For logged-in layouts with a persistent top bar and a main region below it, **avoid** stacking `min-h-screen` with a hand-tuned `calc(100vh − [nav height])` unless the nav height is provably identical at every breakpoint. A few pixels of mismatch introduces a **permanent page-level vertical scrollbar** and erodes polish.
|
|
180
|
+
|
|
181
|
+
> 💡 **Lesson learned:** Prefer a **flex column** on the outer shell (`h-screen` / `min-h-0` discipline), navbar `shrink-0`, main area `flex-1 min-h-0` with overflow where scrolling belongs. **Why:** Flex distributes remaining height without fragile viewport math; margin on inner sections inside a height-bounded flex child can still add to overflow—prefer padding inside scroll regions.
|
|
182
|
+
|
|
183
|
+
### Admin and internal tools: master–detail tables
|
|
184
|
+
|
|
185
|
+
_Use when **operators** manage **accounts, billing, or tenants** in a data-dense UI._
|
|
186
|
+
|
|
187
|
+
> 💡 **Lesson learned:** Avoid **twelve** visible columns of mixed criticality. Prefer a **readable row** (identity, plan, lifecycle state) plus **expand** or **slide-over detail** for seldom-used fields (internal notes, promo codes, deep links to billing provider). Keep **header column count** aligned with body rows when using **colspan** for expanded content — misalignment reads as a broken table.
|
|
188
|
+
>
|
|
189
|
+
> 🔧 **Guidance:** Establish visual hierarchy: primary actions in the row or kebab, destructive actions behind confirm. If **impersonation** exists, use a **persistent banner** or shell token so it is obvious which principal will receive mutations.
|
|
190
|
+
>
|
|
191
|
+
> 💡 **Lesson learned:** **Use a shared badge/chip class for role and status indicators.** When admin tables display multiple chip types (Admin, Demo, Trial, Plan tier), inconsistent sizing, colors, or borders make the table look unpolished. Define one constant (e.g., `CHIP_BADGE = "text-[10px] uppercase tracking-wide px-1 py-0.5 rounded border ..."`) and apply it to all chips, varying only the color accent. **Admin bypass in trial/plan columns:** Show a neutral placeholder ("—") with an explanatory tooltip instead of repeating the same urgency labels non-admin users see. WHY: Admin rows flagged "Expired" in a triage list create false alarms.
|
|
192
|
+
|
|
193
|
+
### Dropdown menus: secondary flyouts (hover sub-panels)
|
|
194
|
+
|
|
195
|
+
When a main menu opens a **second** panel to the side (account switcher, admin links, long pickers), share **visual chrome** (surface, border, radius, shadow) via one CSS class so spacing stays intentional.
|
|
196
|
+
|
|
197
|
+
> 💡 **Lesson learned:** **`position: absolute` + `right-full` (or equivalent) is measured from the flyout trigger’s containing block.** If one section of the menu wraps rows in **horizontal padding** and another does not, the same utility produces **different gaps** relative to the dropdown edge—test “inset” rows vs “full width” footer rows side by side. Compensate with a calculated offset for padded ancestors, or restructure so `.relative` wrappers share width context.
|
|
198
|
+
|
|
199
|
+
Large scrollable flyouts anchored near the **bottom** of the menu should not use only `bottom: 0` growth (content clips off the top of the viewport). **Vertically center** the panel on the trigger row (or clamp between top/bottom margins) with `max-height` and internal scroll; compute `top` from `getBoundingClientRect()` on the trigger wrapper if needed. **Keep horizontal** alignment consistent with sibling flyouts (same margin from the parent column)—do not switch to hand-tuned viewport `right` for vertical fixes; that drifts from `absolute` alignment and widens the perceived gap.
|
|
200
|
+
|
|
201
|
+
> 💡 **Lesson learned:** **Hover flyout state** needs an **identity** per sub-panel. Reset all flags when the **shell** (main menu) closes, not on every `mouseenter`. If opening a flyout runs “close everything” synchronously, re-entering the same trigger after crossing a **pixel gap** between row and panel fires `mouseenter` again and **kills** the panel before the pointer reaches it. Track which flyout is active; if the user re-enters the **same** id, only cancel pending close timers.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Typography
|
|
206
|
+
|
|
207
|
+
- **Font family:** [e.g., Inter with system-ui fallback]
|
|
208
|
+
- **Base letter-spacing:** [e.g., -0.011em]
|
|
209
|
+
- **Heading letter-spacing:** [e.g., -0.025em tighter for authority]
|
|
210
|
+
- **Body line-height:** [e.g., 1.6]
|
|
211
|
+
|
|
212
|
+
**Chrome vs. reading:** Use one stack for **navigation, filters, short labels, and dense UI chrome** (typically sans-serif). Use a **distinct reading stack** for long narrative or interview-prep style content if the product mixes structure with prose—intentional font pairing reads as craft; a single family everywhere reads as generic.
|
|
213
|
+
|
|
214
|
+
> 💡 **Lesson learned:** **Interactive controls** (buttons, inputs, pills, small targets) should share **one default radius**—mixed `rounded` / `rounded-md` / `rounded-lg` reads accidental. Cards and modals can use a larger token deliberately; the hierarchy should be documented, not improvised per screen.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Animations
|
|
219
|
+
|
|
220
|
+
_Define named keyframes in `app.css` and expose as utility classes._
|
|
221
|
+
|
|
222
|
+
| Class | Animation | Duration | Use |
|
|
223
|
+
| ---------------------- | ------------------- | -------- | ----------------- |
|
|
224
|
+
| `.animate-panel-in` | Slide in from right | 260ms | Slide-out panels |
|
|
225
|
+
| `.animate-modal-in` | Scale up from 95% | 200ms | Centered modals |
|
|
226
|
+
| `.animate-backdrop-in` | Fade in | 200ms | Overlay backdrops |
|
|
227
|
+
| `[custom]` | [describe] | [ms] | [use case] |
|
|
228
|
+
| `transition:slide` | Height slide | 200ms | Auth mode switch, accordions |
|
|
229
|
+
|
|
230
|
+
> 💡 **Lesson learned:** CSS-only animations (no JS, no per-component imports) are the right default. Define once in app.css, apply via class on any `{#if}`-mounted element. Spring easing (cubic-bezier) feels more natural than linear or ease-in-out for panel slides.
|
|
231
|
+
>
|
|
232
|
+
> Also useful: a `.reminder-highlight` animation class (2s amber glow pulse via box-shadow + background-color change) for drawing attention to a specific item after navigation. Auto-clear the highlight state after 2.5s to prevent permanent visual noise.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Accessibility Patterns
|
|
237
|
+
|
|
238
|
+
> 💡 **Lesson learned:** Accessibility isn't a separate phase — it's cheapest when baked into the initial build. These patterns emerged from a landing page audit and apply to every public-facing page.
|
|
239
|
+
|
|
240
|
+
### Skip-to-Content Link
|
|
241
|
+
|
|
242
|
+
Every page with a sticky nav should include a visually-hidden "Skip to content" anchor as the first focusable element in the `<body>`. It becomes visible on keyboard focus and jumps past the navigation to `#main-content`. This is a baseline WCAG requirement and trivial to implement — a single `<a>` with `sr-only focus:not-sr-only` classes.
|
|
243
|
+
|
|
244
|
+
### Headingless Sections
|
|
245
|
+
|
|
246
|
+
Every visible content section should have a heading — even "atmospheric" blocks like empathy quotes, testimonial callouts, or transition copy between major sections. A section without a heading:
|
|
247
|
+
|
|
248
|
+
- Is invisible to screen readers navigating by landmark or heading list.
|
|
249
|
+
- Hurts scannability for fast readers who jump section-to-section.
|
|
250
|
+
|
|
251
|
+
Fix: add a visible micro-heading (e.g., small muted uppercase text like "The quiet part") or at minimum an `sr-only` heading. Prefer visible headings — they improve the page rhythm and give the reader an anchor.
|
|
252
|
+
|
|
253
|
+
### Page Landmarks
|
|
254
|
+
|
|
255
|
+
- Wrap all content between `<nav>` and `<footer>` in a `<main id="main-content">` element.
|
|
256
|
+
- Give every `<section>` an `aria-labelledby` pointing at its heading's `id`. Anonymous sections are invisible to screen readers navigating by landmark.
|
|
257
|
+
- The primary `<nav>` should have `aria-label="Main"` to distinguish it from any footer or sidebar navigation.
|
|
258
|
+
|
|
259
|
+
### Password Visibility Toggles
|
|
260
|
+
|
|
261
|
+
Every password input should have a show/hide toggle button positioned inside the input field (absolutely positioned, right-aligned). Key details:
|
|
262
|
+
|
|
263
|
+
- Use a `<button type="button">` (not a checkbox or anchor) to prevent form submission.
|
|
264
|
+
- Toggle the input's `type` between `'text'` and `'password'`.
|
|
265
|
+
- Include `aria-pressed` reflecting the current state, `aria-controls` pointing at the input's `id`, and `aria-label` ("Show password" / "Hide password").
|
|
266
|
+
- Use an eye / eye-off icon pair. Mark the icon `aria-hidden="true"` since the button label already conveys meaning.
|
|
267
|
+
- On form reset, set all visibility states back to hidden.
|
|
268
|
+
|
|
269
|
+
### Focus-visible and composite widgets
|
|
270
|
+
|
|
271
|
+
> 💡 **Lesson learned:** **`focus-visible` rings** should clarify **keyboard-linear** navigation (links, primary buttons, standard inputs). **Composite** editors — pill/tag inputs, multi-value chips, nested micro-buttons — often shouldn’t show a heavy ring on every **pointer** click, or the chrome feels noisy. Use **roving `tabindex`**, `aria-*` on the composite, and scope rings to real tab stops; reserve `outline-none` for inner pieces that aren’t meant to be separate focus destinations.
|
|
272
|
+
|
|
273
|
+
### Modal and dialog focus trap
|
|
274
|
+
|
|
275
|
+
> 💡 **Lesson learned:** Every modal and centered dialog should **trap focus** while open and **return focus to the trigger** on close. Implement once as a reusable action or hook (`use:focusTrap`): on mount, remember `document.activeElement`, move focus to the dialog (or a `[data-autofocus]` target inside it), cycle Tab / Shift+Tab within focusable descendants, and on teardown call `.focus()` on the remembered element if it is still in the document. **Do not** have the trap action also handle Escape — each layer keeps its own Escape handler (see *Global keyboard shortcuts* → minimal-safe `Esc`). WHY: Without trap + return, keyboard users tab into the page behind the overlay; without deferring Escape, global and modal handlers fight.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Tooltip Usage Guidelines
|
|
280
|
+
|
|
281
|
+
> 💡 **Lesson learned:** Tooltips add value when they reveal information the user can't get from the visible UI. They become noise when they restate what's already obvious. Audit tooltip density after building a page — a first pass often over-explains.
|
|
282
|
+
|
|
283
|
+
> 💡 **Lesson learned:** **Avoid creating dedicated Svelte components for individual icon-only buttons (e.g., `AddRowIconButton.svelte`, `EditIconButton.svelte`) that share identical typescript props and tooltip wrappers.** This copy-pastes visual layout code across multiple files. Instead:
|
|
284
|
+
> 1. Create a unified, generic `IconButton.svelte` that accepts props for `icon` (e.g. `'lucide:plus'`, `'lucide:pencil'`), `ariaLabel`, `onclick`, and optional `tooltipTitle` / `tooltipPlacement`.
|
|
285
|
+
> 2. For backward compatibility or semantic clarity, have the old specific files simply wrap `<IconButton icon="[icon-name]" {...$props} />` as thin, single-line pass-throughs.
|
|
286
|
+
> This retains clear import names (making components easy to find and grep) while concentrating 100% of the visual styling, tooltip lifecycle, and button padding inside a single generic component.
|
|
287
|
+
|
|
288
|
+
**When tooltips help:**
|
|
289
|
+
|
|
290
|
+
- Defining domain-specific terms the user may not know (e.g., status labels like "Stealth" or "Hold")
|
|
291
|
+
- Explaining non-obvious icon-only buttons
|
|
292
|
+
- Showing keyboard shortcuts
|
|
293
|
+
- Providing context on constrained UI (chips, badges, truncated text)
|
|
294
|
+
|
|
295
|
+
**When to remove them:**
|
|
296
|
+
|
|
297
|
+
- The tooltip restates the button label or surrounding heading (e.g., "Start Free Trial" tooltip on a button labeled "Start Free Trial")
|
|
298
|
+
- The section context already explains the information (e.g., a "10-day free trial" tooltip when the pricing header already says "10-day free trial")
|
|
299
|
+
- The tooltip explains something users already understand (e.g., "You'll briefly leave this page" on OAuth buttons)
|
|
300
|
+
|
|
301
|
+
**Guideline:** After building a page, count your tooltips. If there are more than ~8 on a single view, audit each one against the criteria above. In Exec Foundry's landing page, 15 tooltips were pruned to 7 — every removal reduced noise without losing information.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Global keyboard shortcuts
|
|
306
|
+
|
|
307
|
+
> 💡 **Lesson learned:** **App-wide shortcuts** (jump to board, open panel, command palette): Centralize registration (e.g. root layout) and **ignore** the shortcut when focus is inside an editable control (`input`, `textarea`, `select`, `[contenteditable]`) unless the action is explicitly for composing text — otherwise you steal keys from data entry. Prefer combos that don’t fight the browser (`Cmd+L`, `Cmd+K` unless you intentionally mirror omnibar behavior). Surface shortcuts in **tooltips** or a **Help → Shortcuts** sheet so power users discover them without guessing.
|
|
308
|
+
|
|
309
|
+
> 💡 **Lesson learned:** Ship the **smallest credible set first** — focus search, create-new, open help (`?`), and a disciplined Escape — rather than a full power-user system. For an audience used to Gmail/Linear/Superhuman, the *absence* of these reads as "not a serious tool"; the minimal set is a **cohesion win**, not a luxury. Implementation guardrails that keep it safe: (1) a shared `isTypingContext(target)` + IME-composition guard so printable keys never fire mid-entry; (2) fire single-letter shortcuts **only on the bare primary surface** (no record drawer, modal, menu, or tour open) so they cannot clash with scoped shortcuts elsewhere; (3) a discoverable **help overlay** that lists both global and context (record-detail) keys.
|
|
310
|
+
|
|
311
|
+
> 💡 **Lesson learned:** A **global `Esc` handler must defer to whatever layer already owns Escape.** Record drawers, confirm dialogs, and connectivity modals each register their own `keydown` listener; if a new app-level handler also acts on `Esc`, you get double-handling (two layers close at once) or stolen intent. Safe layering: the global handler returns early when a record panel or any modal/overlay is open (`if (selectedRecord || blockingOverlayOpen) return`), and otherwise closes the open slide-over/menu. **Full stacking precedence** (a single "close the topmost layer" stack) is a larger build — defer it and ship the minimal-safe version that only acts when nothing else owns the key.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Long-Running Operations (progress)
|
|
316
|
+
|
|
317
|
+
> 💡 **Lesson learned:** Any user-triggered flow that often exceeds **~3 seconds** (bundled export, multi-step document build, external search, batch LLM) should show **staged progress**: short phase labels that advance (**Gathering…** → **Building…** → **Compressing…**) plus a **determinate or indeterminate** bar. Reuse one small progress helper/component so copy and timing cues stay consistent. WHY: Prevents “frozen app” anxiety and reduces support pings; pairs well with entitlements messaging when exports are gated.
|
|
318
|
+
|
|
319
|
+
> 💡 **Lesson learned:** **Align visible milestones with real backend phases**, not only decorative labels. When generation spans distinct server steps (retrieval, structuring, persistence), expose **phase keys or statuses** from the API (or poll a job record) and map each to copy users recognize. Generic rotating messages while the server sits in one slow step feel dishonest and erode trust; jumping straight to “Done” without an intermediate milestone hides real latency spikes. WHY: Users tolerate longer waits when progress honestly reflects work underway.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## First-load skeletons (shape over spinner)
|
|
324
|
+
|
|
325
|
+
> 💡 **Lesson learned:** For a **data-heavy primary surface** (board, list, dashboard), a **skeleton shaped like the destination** beats a centered spinner: it reads as "your content is arriving," anchors layout so nothing jumps when data lands, and *feels* faster. Build a small placeholder component that mirrors the real structure (e.g. a few columns of pulsing card outlines for a kanban; rows for a table) and mark it `aria-hidden`. Reserve plain spinners for **small, bounded** inline waits. WHY: A spinner over a large empty region implies "stuck"; a skeleton implies "loading the thing you expect."
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Canonical empty-state component
|
|
330
|
+
|
|
331
|
+
> 💡 **Lesson learned:** Route **every** empty state through **one component** with a few variants rather than bespoke markup per surface — otherwise icon size, heading scale, body width, and CTA placement drift into "25 apps" inconsistency. A workable variant set: `wrapper` (dashed inline card inside a panel section), `centered` (no box, for "no results" inside an already-decorated surface), and `hero` (large first-run state for the most-seen empty screens — bigger icon, `<h2>` headline, free-form body, plus `actions` and `footnote` slots). Drive first-run copy from the user's onboarding/search stage so the empty screen *teaches the next step* instead of just stating absence. WHY: One component makes consistency the default and a redesign a single edit.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Save acknowledgement (auto-save feedback)
|
|
336
|
+
|
|
337
|
+
> 💡 **Lesson learned:** Wherever fields **save on blur** (Tier-A scratchpad inputs), give the surface **one shared, three-phase acknowledgement** — `Saving…` → `✓ Saved` → `Couldn't save — try again` — with a ~2s fade on success and a longer hold on error. Two failure modes to avoid: a **success-only** ack (no in-flight or error state) lets a rejected save look saved; and an ack on **some** fields but not others reads as flaky. Prefer a single indicator in the panel chrome that **every** field feeds, over a separate spinner per input. Do **not** invent a second spinner style for the same job. (Architecture for bubbling child saves into one indicator lives in `CONTEXT_PROMPT.md` → Patterns to Follow.) WHY: Predictable, ubiquitous "Saved" feedback does more for trust than almost any single feature.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## First-run hints (dismissible coach marks)
|
|
342
|
+
|
|
343
|
+
> 💡 **Lesson learned:** When a dense surface (a multi-tab record detail, a complex panel) has **no obvious "start here,"** a **one-time, dismissible inline hint** on first open ("New here? Start with X, then Y") cuts the wayfinding tax without the weight of a full tour. Keep it subtle (a thin accent-tinted strip, not a modal), make it explicitly dismissible, and persist dismissal **per user** (e.g. `localStorage` keyed by user id) so it never re-nags and so coach/delegate accounts don't inherit each other's state. WHY: Discoverability that relies on the user *choosing* a tour leaves most users lost; a calm inline cue teaches in place.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Board-level "next best move" (recommendation strip)
|
|
348
|
+
|
|
349
|
+
> 💡 **Lesson learned:** Distinct from the **inline first-run hint** on a dense panel: when you already compute **prioritized follow-ups or reminders** server-side, elevate the **single highest-leverage item** into a calm board-level **"Start here"** strip — one click opens the target in context, with an "{N} more" affordance that expands the full list unchanged. Frame it as a **recommendation**, not a notification count; use the same priority sort you already trust. WHY: Overwhelmed users on a data-heavy primary surface need one obvious next move at the *board* level, not only per-card nudges or a collapsed "3 follow-ups" toggle.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## First successful artifact moment
|
|
354
|
+
|
|
355
|
+
> 💡 **Lesson learned:** The first time a user completes your core **generated output** (tailored documents, compiled report, assembled bundle — whatever is the product's "wow"), don't let it silently appear. Show a **one-time, dismissible orientation overlay**: what was built, where each piece lives in the UI, and a primary action to view it. Calm and senior in tone — orientation, not confetti. Persist dismissal **per user**; trigger off a stable completion signal (e.g. a running total from the server, or a domain event with `count === 1`). WHY: The wow converts to understanding only when the user knows where to find the output next time.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## AI-generated section lifecycle (empty / generating / ready / stale)
|
|
360
|
+
|
|
361
|
+
> 💡 **Lesson learned:** Multiple AI surfaces on one record (alignment map, research dossier, personalized brief, document transformation) often express the **same four concepts** with different copy and chrome: **empty** (never run — invite first generation), **generating** (in flight — spinner or staged progress), **ready** (content exists), **stale** (inputs changed since last run — non-blocking refresh, never auto-regen). Centralize a **shared vocabulary module** (sentence structure + refresh labels) and a **canonical stale banner** (amber strip + "Refresh now" / "Refreshing…"). Each surface keeps its own generate-verb ("draft", "map", "build") for voice; the *structure* stays one product behavior. Stale requires **server-side input-hash** (or equivalent) — don't show a stale banner until you can detect drift honestly.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Factual grounding and veracity cards (human-in-the-loop safety net)
|
|
366
|
+
|
|
367
|
+
> 💡 **Lesson learned:** When displaying programmatic veracity or factual grounding audit results, use a structured card component that balances reassurance and caution.
|
|
368
|
+
> - **Visual Hierarchy:** Use a distinct border and background color depending on the veracity state (e.g., green for fully grounded, amber/red for flagged issues).
|
|
369
|
+
> - **Clear Structure:** Show a header identifying the audit (e.g., "Factual Grounding Audit"), a summary of issues, a toggle to expand/collapse details, and a dismiss button.
|
|
370
|
+
> - **Detailed Issue Cards:** For each flagged issue, render:
|
|
371
|
+
> - A severity badge (`high` in red, `medium` in amber, `low` in blue).
|
|
372
|
+
> - The exact excerpt from the generated text in a blockquote with a monospaced font and a distinct left border.
|
|
373
|
+
> - A plain-language explanation of why it was flagged.
|
|
374
|
+
> - A suggested repair in a monospaced font inside a subtle dark container.
|
|
375
|
+
> - **Outcome-Oriented Copy:** Keep copy focused on factual correctness and trust. Avoid technical jargon like "LLM hallucination" or "regex parsing" in user-facing text.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Long-form generated reading (outline & navigation)
|
|
380
|
+
|
|
381
|
+
> 💡 **Lesson learned:** AI-generated or imported **long reports** (research dossiers, compliance summaries, interview briefs) need **in-document navigation**, not only vertical scroll. A **sticky outline** (heading hierarchy from markdown or structured sections) plus **scroll-spy** or active-section highlighting lets users jump without losing context. Include **subsection headings** in the outline when mid-document anchors matter; support **deep links** (`#section-slug`) from reminders, email, or internal cross-references. Test **keyboard focus** when activating outline links (skip links or move focus to content). Collapse or drawer the outline on narrow viewports so the reading column stays primary.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## FAQ / Accordion Pattern
|
|
386
|
+
|
|
387
|
+
> 💡 **Lesson learned:** A 5-7 item FAQ section placed just before the signup form is a high-value, low-effort conversion tool. It catches objections at the moment of decision and provides long-tail SEO content.
|
|
388
|
+
|
|
389
|
+
**Structure:**
|
|
390
|
+
|
|
391
|
+
- Use a simple array of `{ q: string, a: string }` objects for content.
|
|
392
|
+
- Track the open item index in a single state variable (only one item open at a time, or `null` for all closed).
|
|
393
|
+
- Use a `slide` transition (or equivalent height animation) on the answer panel for smooth expand/collapse.
|
|
394
|
+
- Each question is a `<button>` with a chevron indicator that rotates on open.
|
|
395
|
+
- **Large FAQ sets:** If questions span multiple themes, add **category tabs** or grouped sections so users aren’t scrolling through one long stack. Prefer **always-visible answers** or a stable “all open” mode when SEO or skimming matters; if you use tabs, support a **shareable** `?category=` (or hash) so a single answer can be linked from Help or support mail.
|
|
396
|
+
|
|
397
|
+
**Content strategy:**
|
|
398
|
+
|
|
399
|
+
- Lead with the question the target buyer is most likely to have (security/privacy for sensitive data apps, pricing for SaaS).
|
|
400
|
+
- Include "Can I cancel anytime?" — it reduces friction even when the answer is obvious.
|
|
401
|
+
- Include a differentiator question ("How is this different from [alternative]?") — it's a chance to restate your positioning in a format that matches how prospects actually think.
|
|
402
|
+
- Avoid internal jargon in answers. If the user hasn't signed up yet, they don't know your feature names.
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Terminology & Tone in the UI
|
|
407
|
+
|
|
408
|
+
_User-facing labels carry emotional weight. Define conventions here._
|
|
409
|
+
|
|
410
|
+
> 💡 **Lesson learned:** Terminology for negative outcomes deserves careful attention. "Not Selected" instead of "Rejected" — factual and neutral without implying personal failure. "On Hold" instead of "Paused" or "Frozen." Keep the internal enum key unchanged for backward compatibility; only map the display label. This is a design system concern, not just a code concern, because the word choice IS the brand expression.
|
|
411
|
+
|
|
412
|
+
- Negative outcomes: [use neutral, factual language]
|
|
413
|
+
- Blocked states: [reframe as "safe-keeping" or "preserved," not "locked out"]
|
|
414
|
+
- CTAs to resume: [use "Resume" or "Continue," not "Upgrade" when the user is returning, not new]
|