@techninja/clearstack 0.3.36 → 0.3.38
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/bin/cli.js +24 -4
- package/docs/OG_METADATA_SPEC.md +98 -51
- package/docs/SPEC_WORKFLOW.md +116 -0
- package/docs/app-spec/SPEC_WATCH_PROPOSAL.md +80 -0
- package/lib/build-og-images.js +122 -0
- package/lib/build-og.js +23 -43
- package/lib/check.js +10 -10
- package/lib/og-default-template.js +48 -0
- package/lib/og-image-template.js +111 -0
- package/lib/og-template.js +2 -2
- package/lib/report.js +106 -0
- package/lib/spec-config.js +7 -3
- package/lib/spec-utils.js +38 -36
- package/package.json +3 -3
- package/templates/shared/docs/clearstack/OG_METADATA_SPEC.md +98 -51
- package/templates/shared/docs/clearstack/SPEC_WORKFLOW.md +116 -0
|
@@ -8,73 +8,120 @@ only read static HTML — they don't execute JavaScript. Since Clearstack apps a
|
|
|
8
8
|
SPAs with a single `index.html`, every shared link shows the same generic
|
|
9
9
|
metadata regardless of the actual page content.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
## Solution
|
|
12
|
+
|
|
13
|
+
Clearstack generates two things from the route config:
|
|
14
|
+
|
|
15
|
+
1. **Static HTML shells** with OG meta tags (for crawlers)
|
|
16
|
+
2. **Dynamic OG images** (1200×630 PNGs rendered via Playwright)
|
|
17
|
+
|
|
18
|
+
Both are driven by the same `clearstack.routes.json` config and data sources.
|
|
19
|
+
|
|
20
|
+
## Route Config
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"/trait/:slug": {
|
|
25
|
+
"title": "{slug.name}",
|
|
26
|
+
"description": "{slug.description}",
|
|
27
|
+
"image": "{slug.cover_image.url}",
|
|
28
|
+
"data": "src/data/trait_manifest.json:traits",
|
|
29
|
+
"ogTemplate": "trait"
|
|
30
|
+
},
|
|
31
|
+
"/shop/product/:sku": {
|
|
32
|
+
"title": "{sku.name} | {store.name}",
|
|
33
|
+
"description": "{sku.description}",
|
|
34
|
+
"image": "{store.url}{sku.images.0}",
|
|
35
|
+
"data": "src/data/products.json",
|
|
36
|
+
"ogTemplate": "product"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
15
40
|
|
|
16
|
-
|
|
17
|
-
(title, description). The build system should use this to generate static HTML
|
|
18
|
-
shells for each route — just enough for crawlers to read OG tags, while the SPA
|
|
19
|
-
still handles rendering for real users.
|
|
41
|
+
### Fields
|
|
20
42
|
|
|
21
|
-
|
|
43
|
+
| Field | Required | Description |
|
|
44
|
+
| -------------- | -------- | ------------------------------------------------------- |
|
|
45
|
+
| `title` | yes | Template string with `{path.to.data}` interpolation |
|
|
46
|
+
| `description` | yes | Template string for meta description |
|
|
47
|
+
| `image` | no | Template string for og:image URL |
|
|
48
|
+
| `data` | no | Data source — `filepath:jsonPath` (supports arrays/objects) |
|
|
49
|
+
| `ogTemplate` | no | Template name → resolves to `src/og-templates/{name}.html` |
|
|
22
50
|
|
|
23
|
-
|
|
51
|
+
### Data Sources
|
|
24
52
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
title: '{trait.emoji} {trait.name} | {app.name}'
|
|
29
|
-
description: '{trait.description}'
|
|
30
|
-
image: '{trait.cover_image.url}'
|
|
31
|
-
data: trait_manifest.traits
|
|
32
|
-
```
|
|
53
|
+
- **Arrays** — each item becomes a page
|
|
54
|
+
- **Objects** — entries become arrays with the key set as `slug`
|
|
55
|
+
- **JSON path** — dot notation into nested structures (e.g. `manifest.json:traits`)
|
|
33
56
|
|
|
34
|
-
|
|
35
|
-
- For static routes (`/about`, `/pricing`): generate one HTML file
|
|
36
|
-
- For dynamic routes (`/traits/:id`): iterate over the data source, generate
|
|
37
|
-
one HTML file per entry
|
|
57
|
+
## OG Image Templates
|
|
38
58
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- `og:title`, `og:description`, `og:image`, `og:url`
|
|
42
|
-
- `twitter:card`, `twitter:title`, `twitter:description`, `twitter:image`
|
|
43
|
-
- The same `<body>` as index.html (SPA takes over client-side)
|
|
59
|
+
Templates are HTML files at `src/og-templates/` rendered by Playwright at
|
|
60
|
+
1200×630 and screenshotted to PNG.
|
|
44
61
|
|
|
45
|
-
|
|
46
|
-
- Cloudflare Pages / Netlify / Vercel serve the static file if it exists
|
|
47
|
-
- Falls back to `404.html` (SPA) for routes without generated pages
|
|
62
|
+
### Resolution Order
|
|
48
63
|
|
|
49
|
-
|
|
64
|
+
1. `src/og-templates/{ogTemplate}.html` (route-specific)
|
|
65
|
+
2. `src/og-templates/default.html` (project default)
|
|
66
|
+
3. Built-in default (gradient card with title/description)
|
|
50
67
|
|
|
51
|
-
|
|
52
|
-
- Opt-in image support (from data source or static asset)
|
|
53
|
-
- Works with any deploy target that supports static files + SPA fallback
|
|
54
|
-
- Doesn't require a server or edge function
|
|
55
|
-
- Build is fast (64 pages in <1s, 648 pages in <5s)
|
|
68
|
+
### Template Conventions
|
|
56
69
|
|
|
57
|
-
|
|
70
|
+
- Use `{tokenName}` interpolation for data (word chars + dots only)
|
|
71
|
+
- Local assets (`src="/logo.svg"`) are inlined as data URIs automatically
|
|
72
|
+
- Remote images (`https://...`) load via Playwright's network
|
|
73
|
+
- CSS custom properties from project tokens are injected into context
|
|
74
|
+
- Available computed fields: `{variantsFormatted}`, `{uniqueFormatted}`
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
- Next.js `generateMetadata` — server-side, requires Node runtime
|
|
61
|
-
- Astro content collections — static generation from markdown frontmatter
|
|
76
|
+
### Available Context Variables
|
|
62
77
|
|
|
63
|
-
|
|
78
|
+
| Variable | Source |
|
|
79
|
+
| -------------------- | ----------------------------------- |
|
|
80
|
+
| `{title}` | Resolved + truncated route title |
|
|
81
|
+
| `{description}` | Resolved + truncated description |
|
|
82
|
+
| `{image}` | Resolved image URL |
|
|
83
|
+
| `{emoji}` | From item data |
|
|
84
|
+
| `{bg}`, `{primary}` | From project CSS tokens |
|
|
85
|
+
| `{variantsFormatted}`| Formatted number (K/M suffix) |
|
|
86
|
+
| All item fields | Spread directly (e.g. `{name}`, `{pgs_count}`) |
|
|
64
87
|
|
|
65
|
-
|
|
88
|
+
## CLI Commands
|
|
66
89
|
|
|
67
90
|
```bash
|
|
68
|
-
|
|
69
|
-
clearstack build
|
|
91
|
+
# Generate OG HTML pages (for crawlers)
|
|
92
|
+
clearstack build og --url=https://mysite.com --out=dist
|
|
93
|
+
|
|
94
|
+
# Generate OG images (all routes)
|
|
95
|
+
clearstack build og-images --site=MySite --out=dist
|
|
96
|
+
|
|
97
|
+
# Generate a single image (fast iteration)
|
|
98
|
+
clearstack build og-images --slug=EFO_0004279 --site=MySite --out=dist/og
|
|
99
|
+
|
|
100
|
+
# Generate both HTML + images
|
|
101
|
+
clearstack build all --url=https://mysite.com --site=MySite --out=dist
|
|
70
102
|
```
|
|
71
103
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
104
|
+
### Flags
|
|
105
|
+
|
|
106
|
+
| Flag | Description |
|
|
107
|
+
| ---------- | ------------------------------------ |
|
|
108
|
+
| `--out` | Output directory (default: `dist`) |
|
|
109
|
+
| `--url` | Base URL for og:url tags |
|
|
110
|
+
| `--site` | Site name for badge/branding |
|
|
111
|
+
| `--logo` | Logo path or URL |
|
|
112
|
+
| `--slug` | Single item slug (fast iteration) |
|
|
113
|
+
|
|
114
|
+
## Requirements
|
|
115
|
+
|
|
116
|
+
- Zero config for basic cases (route title + description → OG tags)
|
|
117
|
+
- Custom templates per page type via `ogTemplate` field
|
|
118
|
+
- Design tokens (CSS variables) automatically available in templates
|
|
119
|
+
- Local assets inlined as data URIs (no file:// security issues)
|
|
120
|
+
- Fast iteration: `--slug` flag renders one image in ~2s
|
|
121
|
+
- Full batch: 64 pages in <60s
|
|
122
|
+
- Works with any static deploy target (Cloudflare Pages, Netlify, Vercel)
|
|
75
123
|
|
|
76
|
-
##
|
|
124
|
+
## Dependencies
|
|
77
125
|
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
- Image generation (dynamic OG images with text overlay) — future scope?
|
|
126
|
+
- **Playwright** (devDependency) — for OG image rendering
|
|
127
|
+
- No runtime dependencies — generated files are static PNGs + HTML
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Spec Workflow: LLM Sessions & Compliance
|
|
2
|
+
|
|
3
|
+
## The Problem
|
|
4
|
+
|
|
5
|
+
LLM coding sessions move fast. Features emerge through rapid iteration,
|
|
6
|
+
and stopping to run `npm run spec` after every file edit creates friction
|
|
7
|
+
that kills creative velocity. But ignoring the spec entirely leads to
|
|
8
|
+
a costly batch refactor at the end — splitting files, fixing lint, and
|
|
9
|
+
chasing type errors across dozens of changes.
|
|
10
|
+
|
|
11
|
+
## The Balance: Feature Checkpoints
|
|
12
|
+
|
|
13
|
+
Don't run spec after every change. Don't ignore it until deploy. Instead,
|
|
14
|
+
run spec at **natural feature boundaries**:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Feature unit complete → npm run spec code
|
|
18
|
+
Section complete → npm run spec all
|
|
19
|
+
Pre-deploy → npm run spec all + types
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A "feature unit" is a logical chunk: "media grid view works," "portfolio
|
|
23
|
+
section complete," "name correction system done." Not every file save.
|
|
24
|
+
|
|
25
|
+
## Project Rules for LLM Context
|
|
26
|
+
|
|
27
|
+
The biggest spec compliance gap is **the LLM not knowing the rules exist.**
|
|
28
|
+
Spec docs live in `docs/` but aren't automatically loaded into session
|
|
29
|
+
context. Fix this with a project-level rules file:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
.amazonq/rules/clearstack.md # Loaded every session automatically
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This file should state:
|
|
36
|
+
|
|
37
|
+
- The 150-line limit and what to do at 120 lines
|
|
38
|
+
- Decomposition patterns (what to extract when)
|
|
39
|
+
- Code style enforcement (so the LLM writes compliant code from the start)
|
|
40
|
+
- The assumption that spec watch may be running
|
|
41
|
+
|
|
42
|
+
When an LLM knows the constraint exists, it naturally writes smaller
|
|
43
|
+
files, adds `SPLIT CANDIDATE` comments, and avoids patterns that will
|
|
44
|
+
need refactoring later.
|
|
45
|
+
|
|
46
|
+
## The SPLIT CANDIDATE Pattern
|
|
47
|
+
|
|
48
|
+
When a file passes ~120 lines during development:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
// SPLIT CANDIDATE: keyboard handler → utils/mediaKeys.js
|
|
52
|
+
// SPLIT CANDIDATE: carousel template → utils/mediaCarousel.js
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
These comments serve two purposes:
|
|
56
|
+
|
|
57
|
+
1. Signal to the current session where to split if the file goes over
|
|
58
|
+
2. Signal to a future session exactly how to decompose without re-reading
|
|
59
|
+
|
|
60
|
+
## Spec Watch Mode (Proposed)
|
|
61
|
+
|
|
62
|
+
A dashboard that runs continuously during development:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm run spec --watch
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Displays a compact status board:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
✅ code (67 files)
|
|
72
|
+
✅ lint (44 files)
|
|
73
|
+
⚠️ types (1 warning)
|
|
74
|
+
✅ docs (12 files)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
When a violation occurs, it shows the error inline and provides a
|
|
78
|
+
copy-pasteable block the developer can drop into their LLM session:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
❌ code: src/pages/media/media-detail-view.js (167 lines, max 150)
|
|
82
|
+
Split candidates: L45 data loading, L89 carousel template, L140 related grid
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This gives the LLM everything it needs to fix the issue without running
|
|
86
|
+
the check itself, re-reading the file, or guessing at the structure.
|
|
87
|
+
|
|
88
|
+
## Automated Ignore Configuration
|
|
89
|
+
|
|
90
|
+
The spec checker should manage ignore paths centrally rather than
|
|
91
|
+
requiring manual duplication across eslint, prettier, stylelint, and
|
|
92
|
+
jsconfig. A single `SPEC_IGNORE_DIRS` in `.env` should propagate to
|
|
93
|
+
all tools automatically, or the spec checker should inject ignores
|
|
94
|
+
when running each tool.
|
|
95
|
+
|
|
96
|
+
Current pain: adding `src/space/` required editing 5 different config
|
|
97
|
+
files. It should require editing one.
|
|
98
|
+
|
|
99
|
+
## Decomposition Patterns for Views
|
|
100
|
+
|
|
101
|
+
View components (page-level) are the most common files to exceed 150
|
|
102
|
+
lines because they combine: imports, data loading, state management,
|
|
103
|
+
keyboard handlers, and large template literals.
|
|
104
|
+
|
|
105
|
+
Extraction priority (most reusable → most specific):
|
|
106
|
+
|
|
107
|
+
| What to extract | Where to put it | When |
|
|
108
|
+
|----------------|-----------------|------|
|
|
109
|
+
| Data loading/caching | `src/utils/{feature}Loader.js` | >20 lines of fetch/transform |
|
|
110
|
+
| Keyboard/event handlers | `src/utils/{feature}Keys.js` | >10 lines of handler logic |
|
|
111
|
+
| Repeated template fragments | `src/utils/{feature}Template.js` | Used in 2+ views |
|
|
112
|
+
| Render sub-sections | `src/utils/{feature}Render.js` | Single view, but >30 lines |
|
|
113
|
+
| Shared UI (header, breadcrumb) | `src/components/` | Used site-wide |
|
|
114
|
+
|
|
115
|
+
The goal isn't to have zero logic in view files — it's to keep each
|
|
116
|
+
file answerable with "what does this do?" in one pass.
|