@drunkcoding/agents-and-skills 0.0.45 → 0.0.46
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/.claude-plugin/marketplace.json +2 -47
- package/README.md +3 -6
- package/package.json +4 -1
- package/plugins/multica-tool/.claude-plugin/plugin.json +1 -1
- package/plugins/multica-tool/scripts/lib.mjs +53 -4
- package/plugins/multica-tool/scripts/multica-export.mjs +135 -32
- package/plugins/multica-tool/scripts/multica-import.mjs +192 -17
- package/plugins/multica-tool/skills/export/SKILL.md +84 -14
- package/plugins/multica-tool/skills/import/SKILL.md +22 -7
- package/plugins/multica-tool/skills/sync/SKILL.md +1 -1
- package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
- package/plugins/html-effectiveness/.claude-plugin/plugin.json +0 -17
- package/plugins/html-effectiveness/README.md +0 -28
- package/plugins/html-effectiveness/agents/report-builder.md +0 -66
- package/plugins/html-effectiveness/assets/base.css +0 -51
- package/plugins/html-effectiveness/assets/base.js +0 -11
- package/plugins/html-effectiveness/assets/charts.js +0 -1
- package/plugins/html-effectiveness/assets/components.css +0 -10
- package/plugins/html-effectiveness/commands/html-report.md +0 -13
- package/plugins/html-effectiveness/docs/template-gallery.md +0 -28
- package/plugins/html-effectiveness/package.json +0 -6
- package/plugins/html-effectiveness/scripts/_tmp.js +0 -37
- package/plugins/html-effectiveness/scripts/catalog.js +0 -47
- package/plugins/html-effectiveness/scripts/extract.js +0 -104
- package/plugins/html-effectiveness/scripts/mustache.js +0 -90
- package/plugins/html-effectiveness/scripts/render.js +0 -176
- package/plugins/html-effectiveness/skills/html-effectiveness/SKILL.md +0 -94
- package/plugins/html-effectiveness/templates/01-exploration-code-approaches.html.tmpl +0 -400
- package/plugins/html-effectiveness/templates/02-exploration-visual-designs.html.tmpl +0 -456
- package/plugins/html-effectiveness/templates/03-code-review-pr.html.tmpl +0 -581
- package/plugins/html-effectiveness/templates/04-code-understanding.html.tmpl +0 -443
- package/plugins/html-effectiveness/templates/05-design-system.html.tmpl +0 -578
- package/plugins/html-effectiveness/templates/06-component-variants.html.tmpl +0 -557
- package/plugins/html-effectiveness/templates/07-prototype-animation.html.tmpl +0 -417
- package/plugins/html-effectiveness/templates/08-prototype-interaction.html.tmpl +0 -363
- package/plugins/html-effectiveness/templates/09-slide-deck.html.tmpl +0 -535
- package/plugins/html-effectiveness/templates/10-svg-illustrations.html.tmpl +0 -444
- package/plugins/html-effectiveness/templates/11-status-report.html.tmpl +0 -152
- package/plugins/html-effectiveness/templates/12-incident-report.html.tmpl +0 -547
- package/plugins/html-effectiveness/templates/13-flowchart-diagram.html.tmpl +0 -361
- package/plugins/html-effectiveness/templates/14-research-feature-explainer.html.tmpl +0 -348
- package/plugins/html-effectiveness/templates/15-research-concept-explainer.html.tmpl +0 -334
- package/plugins/html-effectiveness/templates/16-implementation-plan.html.tmpl +0 -657
- package/plugins/html-effectiveness/templates/17-pr-writeup.html.tmpl +0 -553
- package/plugins/html-effectiveness/templates/18-editor-triage-board.html.tmpl +0 -541
- package/plugins/html-effectiveness/templates/19-editor-feature-flags.html.tmpl +0 -627
- package/plugins/html-effectiveness/templates/20-editor-prompt-tuner.html.tmpl +0 -687
- package/plugins/html-effectiveness/templates/manifest.json +0 -441
- package/plugins/team-share/.claude-plugin/plugin.json +0 -18
- package/plugins/team-share/README.md +0 -46
- package/plugins/team-share/agents/team-share.md +0 -53
- package/plugins/team-share/commands/team-share.md +0 -4
- package/plugins/team-share/skills/claude-config/SKILL.md +0 -143
- package/plugins/team-share/skills/codegraph-setup/SKILL.md +0 -81
- package/plugins/team-share/skills/understand-setup/SKILL.md +0 -115
- package/plugins/tech-graph/.claude-plugin/plugin.json +0 -17
- package/plugins/tech-graph/README.md +0 -66
- package/plugins/tech-graph/agents/tech-graph.md +0 -97
- package/plugins/tech-graph/commands/tech-graph.md +0 -13
- package/plugins/tech-graph/scripts/check-deps.sh +0 -55
- package/plugins/tech-graph/skills/tech-graph/LICENSE +0 -21
- package/plugins/tech-graph/skills/tech-graph/README.md +0 -555
- package/plugins/tech-graph/skills/tech-graph/README.zh.md +0 -514
- package/plugins/tech-graph/skills/tech-graph/SKILL.md +0 -263
- package/plugins/tech-graph/skills/tech-graph/agents/openai.yaml +0 -4
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style1-flat.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style2-dark.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style4-notion.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style5-glass.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style6-claude.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style7-openai.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/fixtures/agent-memory-types-style4.json +0 -181
- package/plugins/tech-graph/skills/tech-graph/fixtures/api-flow-style7.json +0 -40
- package/plugins/tech-graph/skills/tech-graph/fixtures/mem0-style1.json +0 -297
- package/plugins/tech-graph/skills/tech-graph/fixtures/microservices-style3.json +0 -64
- package/plugins/tech-graph/skills/tech-graph/fixtures/multi-agent-style5.json +0 -45
- package/plugins/tech-graph/skills/tech-graph/fixtures/system-architecture-style6.json +0 -48
- package/plugins/tech-graph/skills/tech-graph/fixtures/tool-call-style2.json +0 -182
- package/plugins/tech-graph/skills/tech-graph/package.json +0 -42
- package/plugins/tech-graph/skills/tech-graph/references/diagram-types.md +0 -171
- package/plugins/tech-graph/skills/tech-graph/references/icons.md +0 -406
- package/plugins/tech-graph/skills/tech-graph/references/style-1-flat-icon.md +0 -108
- package/plugins/tech-graph/skills/tech-graph/references/style-2-dark-terminal.md +0 -107
- package/plugins/tech-graph/skills/tech-graph/references/style-3-blueprint.md +0 -113
- package/plugins/tech-graph/skills/tech-graph/references/style-4-notion-clean.md +0 -95
- package/plugins/tech-graph/skills/tech-graph/references/style-5-glassmorphism.md +0 -125
- package/plugins/tech-graph/skills/tech-graph/references/style-6-claude-official.md +0 -209
- package/plugins/tech-graph/skills/tech-graph/references/style-7-openai.md +0 -215
- package/plugins/tech-graph/skills/tech-graph/references/style-diagram-matrix.md +0 -135
- package/plugins/tech-graph/skills/tech-graph/references/svg-layout-best-practices.md +0 -100
- package/plugins/tech-graph/skills/tech-graph/references/svg-to-png.md +0 -115
- package/plugins/tech-graph/skills/tech-graph/scripts/README.md +0 -269
- package/plugins/tech-graph/skills/tech-graph/scripts/generate-diagram.sh +0 -181
- package/plugins/tech-graph/skills/tech-graph/scripts/generate-from-template.py +0 -1587
- package/plugins/tech-graph/skills/tech-graph/scripts/test-all-styles.sh +0 -143
- package/plugins/tech-graph/skills/tech-graph/scripts/validate-svg.sh +0 -306
- package/plugins/tech-graph/skills/tech-graph/templates/agent-architecture.svg +0 -28
- package/plugins/tech-graph/skills/tech-graph/templates/architecture.svg +0 -23
- package/plugins/tech-graph/skills/tech-graph/templates/comparison-matrix.svg +0 -14
- package/plugins/tech-graph/skills/tech-graph/templates/data-flow.svg +0 -28
- package/plugins/tech-graph/skills/tech-graph/templates/er-diagram.svg +0 -21
- package/plugins/tech-graph/skills/tech-graph/templates/flowchart.svg +0 -21
- package/plugins/tech-graph/skills/tech-graph/templates/sequence.svg +0 -20
- package/plugins/tech-graph/skills/tech-graph/templates/state-machine.svg +0 -20
- package/plugins/tech-graph/skills/tech-graph/templates/timeline.svg +0 -19
- package/plugins/tech-graph/skills/tech-graph/templates/use-case.svg +0 -21
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// CLI + library: render a template + data into a self-contained HTML file.
|
|
3
|
-
// Exit codes: 0 ok, 1 generic, 2 template-not-found, 3 slot schema mismatch, 4 fs write error.
|
|
4
|
-
|
|
5
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
6
|
-
import { dirname, resolve, join } from 'node:path';
|
|
7
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
-
import { parseArgs } from 'node:util';
|
|
9
|
-
import { render as mustache } from './mustache.js';
|
|
10
|
-
|
|
11
|
-
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const PLUGIN_ROOT = dirname(SCRIPT_DIR);
|
|
13
|
-
|
|
14
|
-
const ASSET_BUNDLES = {
|
|
15
|
-
base: { css: 'assets/base.css', js: 'assets/base.js' },
|
|
16
|
-
components: { css: 'assets/components.css' },
|
|
17
|
-
charts: { js: 'assets/charts.js' },
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
function validateSlot(templateId, slotPath, schema, value) {
|
|
21
|
-
if (schema.required && (value === undefined || value === null)) {
|
|
22
|
-
const err = new Error(`${templateId}:${slotPath} required but missing`);
|
|
23
|
-
err.code = 3;
|
|
24
|
-
throw err;
|
|
25
|
-
}
|
|
26
|
-
if (value === undefined || value === null) return;
|
|
27
|
-
const type = schema.type;
|
|
28
|
-
if (type === 'string' || type === 'html') {
|
|
29
|
-
if (typeof value !== 'string') {
|
|
30
|
-
const err = new Error(`${templateId}:${slotPath} expected string got ${typeof value}`);
|
|
31
|
-
err.code = 3;
|
|
32
|
-
throw err;
|
|
33
|
-
}
|
|
34
|
-
} else if (type === 'number') {
|
|
35
|
-
if (typeof value !== 'number') {
|
|
36
|
-
const err = new Error(`${templateId}:${slotPath} expected number got ${typeof value}`);
|
|
37
|
-
err.code = 3;
|
|
38
|
-
throw err;
|
|
39
|
-
}
|
|
40
|
-
} else if (type === 'boolean') {
|
|
41
|
-
if (typeof value !== 'boolean') {
|
|
42
|
-
const err = new Error(`${templateId}:${slotPath} expected boolean got ${typeof value}`);
|
|
43
|
-
err.code = 3;
|
|
44
|
-
throw err;
|
|
45
|
-
}
|
|
46
|
-
} else if (type === 'array') {
|
|
47
|
-
if (!Array.isArray(value)) {
|
|
48
|
-
const err = new Error(`${templateId}:${slotPath} expected array got ${typeof value}`);
|
|
49
|
-
err.code = 3;
|
|
50
|
-
throw err;
|
|
51
|
-
}
|
|
52
|
-
if (schema.of) {
|
|
53
|
-
value.forEach((item, i) => {
|
|
54
|
-
for (const [k, subType] of Object.entries(schema.of)) {
|
|
55
|
-
const subSchema = typeof subType === 'string' ? { type: subType } : subType;
|
|
56
|
-
validateSlot(templateId, `${slotPath}[${i}].${k}`, subSchema, item?.[k]);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
} else if (type === 'object') {
|
|
61
|
-
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
62
|
-
const err = new Error(`${templateId}:${slotPath} expected object got ${typeof value}`);
|
|
63
|
-
err.code = 3;
|
|
64
|
-
throw err;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function validate(templateId, slots, data) {
|
|
70
|
-
for (const [name, schemaRaw] of Object.entries(slots)) {
|
|
71
|
-
const schema = typeof schemaRaw === 'string' ? { type: schemaRaw } : schemaRaw;
|
|
72
|
-
validateSlot(templateId, name, schema, data[name]);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function loadAssets(pluginRoot, bundles, extraJs) {
|
|
77
|
-
const css = [];
|
|
78
|
-
const js = [];
|
|
79
|
-
for (const name of bundles || []) {
|
|
80
|
-
const b = ASSET_BUNDLES[name];
|
|
81
|
-
if (!b) continue;
|
|
82
|
-
if (b.css) css.push(readFileSync(join(pluginRoot, b.css), 'utf8'));
|
|
83
|
-
if (b.js) js.push(readFileSync(join(pluginRoot, b.js), 'utf8'));
|
|
84
|
-
}
|
|
85
|
-
if (extraJs) js.push(readFileSync(join(pluginRoot, extraJs), 'utf8'));
|
|
86
|
-
return { css: css.join('\n'), js: js.join('\n') };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function escapeForTitle(s) {
|
|
90
|
-
return String(s).replace(/[&<>"']/g, (c) => ({
|
|
91
|
-
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
92
|
-
}[c]));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function renderReport({ templateId, data, outPath, manifestPath, pluginRoot }) {
|
|
96
|
-
const root = pluginRoot || PLUGIN_ROOT;
|
|
97
|
-
const mPath = manifestPath || join(root, 'templates/manifest.json');
|
|
98
|
-
const manifest = JSON.parse(readFileSync(mPath, 'utf8'));
|
|
99
|
-
const entry = manifest[templateId];
|
|
100
|
-
if (!entry) {
|
|
101
|
-
const ids = Object.keys(manifest).filter((k) => k !== '_meta');
|
|
102
|
-
const err = new Error(`template not found: ${templateId}. valid: ${ids.join(', ')}`);
|
|
103
|
-
err.code = 2;
|
|
104
|
-
throw err;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
validate(templateId, entry.slots, data);
|
|
108
|
-
|
|
109
|
-
const tmplRelPath = entry.tmpl || `templates/${templateId}.html.tmpl`;
|
|
110
|
-
const tmpl = readFileSync(join(root, tmplRelPath), 'utf8');
|
|
111
|
-
const body = mustache(tmpl, data);
|
|
112
|
-
|
|
113
|
-
const { css, js } = loadAssets(root, entry.asset_bundles, entry.extra_js);
|
|
114
|
-
const title = escapeForTitle(data.title || entry.title || 'Report');
|
|
115
|
-
|
|
116
|
-
const html = `<!doctype html>
|
|
117
|
-
<html lang="en">
|
|
118
|
-
<head>
|
|
119
|
-
<meta charset="utf-8">
|
|
120
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
121
|
-
<title>${title}</title>
|
|
122
|
-
<style>${css}</style>
|
|
123
|
-
</head>
|
|
124
|
-
<body>
|
|
125
|
-
${body}
|
|
126
|
-
<script>${js}</script>
|
|
127
|
-
</body>
|
|
128
|
-
</html>
|
|
129
|
-
`;
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
mkdirSync(dirname(outPath), { recursive: true });
|
|
133
|
-
writeFileSync(outPath, html, 'utf8');
|
|
134
|
-
const sidecar = {
|
|
135
|
-
template: templateId,
|
|
136
|
-
rendered_at: new Date().toISOString(),
|
|
137
|
-
render_version: '0.1.0',
|
|
138
|
-
data,
|
|
139
|
-
};
|
|
140
|
-
writeFileSync(outPath + '.data.json', JSON.stringify(sidecar, null, 2), 'utf8');
|
|
141
|
-
} catch (e) {
|
|
142
|
-
const err = new Error(`failed to write ${outPath}: ${e.message}`);
|
|
143
|
-
err.code = 4;
|
|
144
|
-
throw err;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return outPath;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function cli() {
|
|
151
|
-
const { values } = parseArgs({
|
|
152
|
-
options: {
|
|
153
|
-
template: { type: 'string' },
|
|
154
|
-
data: { type: 'string' },
|
|
155
|
-
out: { type: 'string' },
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
if (!values.template || !values.data || !values.out) {
|
|
159
|
-
process.stderr.write('usage: render.js --template=<id> --data=<json-path> --out=<html-path>\n');
|
|
160
|
-
process.exit(1);
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
const data = JSON.parse(readFileSync(resolve(values.data), 'utf8'));
|
|
164
|
-
const finalPath = renderReport({
|
|
165
|
-
templateId: values.template,
|
|
166
|
-
data,
|
|
167
|
-
outPath: resolve(values.out),
|
|
168
|
-
});
|
|
169
|
-
process.stdout.write(finalPath + '\n');
|
|
170
|
-
} catch (e) {
|
|
171
|
-
process.stderr.write((e.stack || e.message) + '\n');
|
|
172
|
-
process.exit(e.code ?? 1);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (import.meta.url === pathToFileURL(process.argv[1]).href) cli();
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: html-effectiveness
|
|
3
|
-
description: Use when the user wants dense information, status, comparison, plan, code review, or research output rendered as a self-contained interactive HTML report instead of plain markdown. Renders via plugin templates and scripts/render.js into a single HTML file under docs/reports/ (overridable via CLAUDE.md).
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# HTML Effectiveness — Interactive Output
|
|
7
|
-
|
|
8
|
-
Turn dense information into a self-contained, browser-based HTML document the user can explore interactively. Markdown is a wall of text; HTML is a spatial, interactive document. Zero dependencies, single file, opens anywhere.
|
|
9
|
-
|
|
10
|
-
## When to invoke
|
|
11
|
-
|
|
12
|
-
Invoke this skill when the user asks for any of:
|
|
13
|
-
|
|
14
|
-
- "Make a report", "render as HTML", "interactive view"
|
|
15
|
-
- Status updates: weekly status, monthly recap, project digest
|
|
16
|
-
- Incident: post-mortem, outage writeup
|
|
17
|
-
- Code: PR review, code understanding, exploration of implementation approaches
|
|
18
|
-
- Design: design system, component variants, prototypes
|
|
19
|
-
- Research: feature explainer, concept explainer, tutorial
|
|
20
|
-
- Planning: implementation plan, rollout, roadmap
|
|
21
|
-
- Decisions: comparison board, decision matrix
|
|
22
|
-
- Visual: slide deck, flowchart, SVG illustration
|
|
23
|
-
- Editor tooling: triage board, feature flags, prompt tuner
|
|
24
|
-
|
|
25
|
-
If the request fits a report-style output and rendering as HTML would be clearer than markdown, invoke the skill and delegate to the `report-builder` agent.
|
|
26
|
-
|
|
27
|
-
## Template catalog
|
|
28
|
-
|
|
29
|
-
20 templates ship with this plugin. Full table in `plugins/html-effectiveness/docs/template-gallery.md` (regen via `node plugins/html-effectiveness/scripts/catalog.js`). Highlights:
|
|
30
|
-
|
|
31
|
-
- `01` / `02` — exploration comparisons (code approaches, visual designs)
|
|
32
|
-
- `03` — code review board
|
|
33
|
-
- `04` — code understanding / architecture walk-through
|
|
34
|
-
- `05` / `06` — design system, component variants
|
|
35
|
-
- `07` / `08` — prototype animation, prototype interaction
|
|
36
|
-
- `09` — slide deck (arrow-key nav)
|
|
37
|
-
- `10` — SVG illustrations
|
|
38
|
-
- `11` — status report
|
|
39
|
-
- `12` — incident report
|
|
40
|
-
- `13` — flowchart diagram
|
|
41
|
-
- `14` / `15` — research explainers (feature, concept)
|
|
42
|
-
- `16` — implementation plan (annotated timeline)
|
|
43
|
-
- `17` — PR writeup
|
|
44
|
-
- `18` / `19` / `20` — editor tools (triage board, feature flags, prompt tuner)
|
|
45
|
-
|
|
46
|
-
## Render contract
|
|
47
|
-
|
|
48
|
-
Always uphold these invariants:
|
|
49
|
-
|
|
50
|
-
1. **Inline-bundle** — the final HTML is single-file, self-contained. No `<link>` or `<script src>` references. The render script inlines `base.css`, `components.css`, `base.js`, plus any per-template extras.
|
|
51
|
-
2. **Escape strings** — string slots are HTML-escaped. Raw-HTML slots are only allowed when the manifest flags the slot as `"html"`.
|
|
52
|
-
3. **Output path** — read repo `CLAUDE.md` for `html-effectiveness.reportDir`; fallback to `docs/reports/`. Filename: `YYYY-MM-DD-<slug>.html`; slug derived from title (kebab-case, ASCII-only, max 60 chars).
|
|
53
|
-
4. **Sidecar** — `<report>.html.data.json` is always written alongside the HTML so the report can be regenerated by re-running `render.js` on the sidecar.
|
|
54
|
-
|
|
55
|
-
## Workflow
|
|
56
|
-
|
|
57
|
-
1. Pick template via `manifest.json` (match user intent against `use_cases` and `pattern`).
|
|
58
|
-
2. Fill slot data from the conversation; record it as JSON.
|
|
59
|
-
3. Invoke render script:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
node plugins/html-effectiveness/scripts/render.js \
|
|
63
|
-
--template=<id> \
|
|
64
|
-
--data=<data.json> \
|
|
65
|
-
--out=<reportDir>/YYYY-MM-DD-<slug>.html
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
4. Confirm output path to the user; offer `snip render --format html < <path>` to preview in browser.
|
|
69
|
-
|
|
70
|
-
## Design principles
|
|
71
|
-
|
|
72
|
-
Preserved from upstream `ThariqS/html-effectiveness`:
|
|
73
|
-
|
|
74
|
-
- Avoid purple/blue gradient backgrounds, generic feature grids, center-everything layouts, decorative patterns.
|
|
75
|
-
- Prioritize real data, scannable hierarchy, spatial grouping, progressive disclosure over decoration.
|
|
76
|
-
|
|
77
|
-
## Technical requirements
|
|
78
|
-
|
|
79
|
-
- Single `.html` file, fully self-contained.
|
|
80
|
-
- CSS variables for theming (light-only baseline, matching upstream palette).
|
|
81
|
-
- System font stack and 150ms smooth transitions.
|
|
82
|
-
- Responsive design 375px–1440px.
|
|
83
|
-
- Semantic HTML5 with ARIA labels.
|
|
84
|
-
- Print styles that expand all hidden content.
|
|
85
|
-
|
|
86
|
-
## Failure modes
|
|
87
|
-
|
|
88
|
-
- **No template fits**: present the top 2 closest matches with one-line summaries; ask the user to confirm, or offer plain-markdown fallback. Never silently force a poor fit.
|
|
89
|
-
- **Missing slot data**: ask one question at a time; respect the cap of 6 questions before forcing render with best-guess defaults plus a note of what was assumed.
|
|
90
|
-
- **Render error**: surface `template:slot expected <type> got <type>` to the user; fix the slot value; retry.
|
|
91
|
-
|
|
92
|
-
## Vendored templates
|
|
93
|
-
|
|
94
|
-
Templates are vendored, not auto-synced. The pinned upstream SHA lives at `templates/manifest.json` → `_meta.upstream_sha`. To refresh a single template: `node plugins/html-effectiveness/scripts/extract.js fetch <id>.html` then `split <id>.html`. Files land in `.tmp/html-effectiveness/extract/` (gitignored). Hand-merge the diff back into `templates/<id>.html.tmpl` and update `_meta.upstream_sha` if pinning a new revision.
|
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
template: 01-exploration-code-approaches
|
|
3
|
-
upstream: 01-exploration-code-approaches.html
|
|
4
|
-
slots:
|
|
5
|
-
title: string # replaces first <h1> innerHTML
|
|
6
|
-
-->
|
|
7
|
-
<div class="page">
|
|
8
|
-
|
|
9
|
-
<header class="page-head">
|
|
10
|
-
<div class="eyebrow">Exploration · Acme web client</div>
|
|
11
|
-
<h1>{{title}}</h1>
|
|
12
|
-
<div class="prompt-box">
|
|
13
|
-
<span class="label">Prompt</span>
|
|
14
|
-
Show me three different ways to implement debounced search for the task
|
|
15
|
-
filter input in our React codebase, with tradeoffs for each.
|
|
16
|
-
</div>
|
|
17
|
-
</header>
|
|
18
|
-
|
|
19
|
-
<!-- ============================================================= -->
|
|
20
|
-
|
|
21
|
-
<section class="approaches">
|
|
22
|
-
|
|
23
|
-
<!-- ---------- Approach 1 ---------- -->
|
|
24
|
-
<article class="approach">
|
|
25
|
-
<header class="approach-head">
|
|
26
|
-
<h2><span class="num">01</span>Inline useEffect + setTimeout</h2>
|
|
27
|
-
<p>Debounce logic lives directly inside the component that owns the input.</p>
|
|
28
|
-
</header>
|
|
29
|
-
|
|
30
|
-
<div class="code"><pre><span class="kw">export function</span> <span class="fn">TaskSearch</span>() {
|
|
31
|
-
<span class="kw">const</span> [draft, setDraft] = <span class="fn">useState</span>(<span class="str">''</span>);
|
|
32
|
-
<span class="kw">const</span> [query, setQuery] = <span class="fn">useState</span>(<span class="str">''</span>);
|
|
33
|
-
|
|
34
|
-
<span class="fn">useEffect</span>(() <span class="kw">=></span> {
|
|
35
|
-
<span class="kw">const</span> id = <span class="fn">setTimeout</span>(() <span class="kw">=></span> setQuery(draft), <span class="str">300</span>);
|
|
36
|
-
<span class="kw">return</span> () <span class="kw">=></span> <span class="fn">clearTimeout</span>(id);
|
|
37
|
-
}, [draft]);
|
|
38
|
-
|
|
39
|
-
<span class="kw">const</span> { data } = <span class="fn">useTasks</span>({ search: query });
|
|
40
|
-
|
|
41
|
-
<span class="kw">return</span> (
|
|
42
|
-
<<span class="fn">input</span>
|
|
43
|
-
value={draft}
|
|
44
|
-
onChange={(e) <span class="kw">=></span> setDraft(e.target.value)}
|
|
45
|
-
placeholder=<span class="str">"Filter tasks…"</span>
|
|
46
|
-
/>
|
|
47
|
-
);
|
|
48
|
-
}</pre></div>
|
|
49
|
-
|
|
50
|
-
<div class="tradeoffs">
|
|
51
|
-
<div class="row head">
|
|
52
|
-
<div class="cell">Pro</div>
|
|
53
|
-
<div class="cell">Con</div>
|
|
54
|
-
</div>
|
|
55
|
-
<div class="row">
|
|
56
|
-
<div class="cell pro">Zero new abstractions to learn</div>
|
|
57
|
-
<div class="cell con">Logic duplicated everywhere search exists</div>
|
|
58
|
-
</div>
|
|
59
|
-
<div class="row">
|
|
60
|
-
<div class="cell pro">Easy to step through in devtools</div>
|
|
61
|
-
<div class="cell con">Two pieces of state for one conceptual value</div>
|
|
62
|
-
</div>
|
|
63
|
-
<div class="row">
|
|
64
|
-
<div class="cell pro">No dependency or bundle change</div>
|
|
65
|
-
<div class="cell con">Delay constant is buried in component body</div>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<div class="chips">
|
|
70
|
-
<span class="chip">Bundle impact: <strong>+0 kb</strong></span>
|
|
71
|
-
<span class="chip">Testability: <strong>medium</strong></span>
|
|
72
|
-
<span class="chip">Reuse: <strong>low</strong></span>
|
|
73
|
-
<span class="chip">SSR safe: <strong>yes</strong></span>
|
|
74
|
-
</div>
|
|
75
|
-
</article>
|
|
76
|
-
|
|
77
|
-
<!-- ---------- Approach 2 ---------- -->
|
|
78
|
-
<article class="approach">
|
|
79
|
-
<header class="approach-head">
|
|
80
|
-
<h2><span class="num">02</span>Custom useDebounce hook</h2>
|
|
81
|
-
<p>Extract the timer into a shared hook under <code style="font-family:var(--mono)">src/hooks/</code>.</p>
|
|
82
|
-
</header>
|
|
83
|
-
|
|
84
|
-
<div class="code"><pre><span class="cm">// src/hooks/useDebounce.ts</span>
|
|
85
|
-
<span class="kw">export function</span> <span class="fn">useDebounce</span><T>(value: T, ms = <span class="str">300</span>): T {
|
|
86
|
-
<span class="kw">const</span> [debounced, setDebounced] = <span class="fn">useState</span>(value);
|
|
87
|
-
<span class="fn">useEffect</span>(() <span class="kw">=></span> {
|
|
88
|
-
<span class="kw">const</span> id = <span class="fn">setTimeout</span>(() <span class="kw">=></span> setDebounced(value), ms);
|
|
89
|
-
<span class="kw">return</span> () <span class="kw">=></span> <span class="fn">clearTimeout</span>(id);
|
|
90
|
-
}, [value, ms]);
|
|
91
|
-
<span class="kw">return</span> debounced;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
<span class="cm">// TaskSearch.tsx</span>
|
|
95
|
-
<span class="kw">const</span> [draft, setDraft] = <span class="fn">useState</span>(<span class="str">''</span>);
|
|
96
|
-
<span class="kw">const</span> query = <span class="fn">useDebounce</span>(draft, <span class="str">300</span>);
|
|
97
|
-
<span class="kw">const</span> { data } = <span class="fn">useTasks</span>({ search: query });</pre></div>
|
|
98
|
-
|
|
99
|
-
<div class="tradeoffs">
|
|
100
|
-
<div class="row head">
|
|
101
|
-
<div class="cell">Pro</div>
|
|
102
|
-
<div class="cell">Con</div>
|
|
103
|
-
</div>
|
|
104
|
-
<div class="row">
|
|
105
|
-
<div class="cell pro">Single import reused across filter, command bar, board search</div>
|
|
106
|
-
<div class="cell con">One more file to maintain and document</div>
|
|
107
|
-
</div>
|
|
108
|
-
<div class="row">
|
|
109
|
-
<div class="cell pro">Trivial to unit test with fake timers</div>
|
|
110
|
-
<div class="cell con">Generic <code style="font-family:var(--mono)">T</code> hides intent slightly</div>
|
|
111
|
-
</div>
|
|
112
|
-
<div class="row">
|
|
113
|
-
<div class="cell pro">Delay is a visible, tunable argument</div>
|
|
114
|
-
<div class="cell con">Still re-renders on every keystroke</div>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
<div class="chips">
|
|
119
|
-
<span class="chip">Bundle impact: <strong>+0.2 kb</strong></span>
|
|
120
|
-
<span class="chip">Testability: <strong>high</strong></span>
|
|
121
|
-
<span class="chip">Reuse: <strong>high</strong></span>
|
|
122
|
-
<span class="chip">SSR safe: <strong>yes</strong></span>
|
|
123
|
-
</div>
|
|
124
|
-
</article>
|
|
125
|
-
|
|
126
|
-
<!-- ---------- Approach 3 ---------- -->
|
|
127
|
-
<article class="approach">
|
|
128
|
-
<header class="approach-head">
|
|
129
|
-
<h2><span class="num">03</span>Tiny external library</h2>
|
|
130
|
-
<p>Adopt <code style="font-family:var(--mono)">use-debounce</code> for both values and callbacks.</p>
|
|
131
|
-
</header>
|
|
132
|
-
|
|
133
|
-
<div class="code"><pre><span class="kw">import</span> { useDebouncedCallback }
|
|
134
|
-
<span class="kw">from</span> <span class="str">'use-debounce'</span>;
|
|
135
|
-
|
|
136
|
-
<span class="kw">export function</span> <span class="fn">TaskSearch</span>() {
|
|
137
|
-
<span class="kw">const</span> [query, setQuery] = <span class="fn">useState</span>(<span class="str">''</span>);
|
|
138
|
-
|
|
139
|
-
<span class="kw">const</span> onChange = <span class="fn">useDebouncedCallback</span>(
|
|
140
|
-
(next: <span class="kw">string</span>) <span class="kw">=></span> setQuery(next),
|
|
141
|
-
<span class="str">300</span>,
|
|
142
|
-
{ leading: <span class="kw">false</span>, maxWait: <span class="str">1000</span> },
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
<span class="kw">const</span> { data } = <span class="fn">useTasks</span>({ search: query });
|
|
146
|
-
|
|
147
|
-
<span class="kw">return</span> (
|
|
148
|
-
<<span class="fn">input</span>
|
|
149
|
-
defaultValue=<span class="str">""</span>
|
|
150
|
-
onChange={(e) <span class="kw">=></span> onChange(e.target.value)}
|
|
151
|
-
/>
|
|
152
|
-
);
|
|
153
|
-
}</pre></div>
|
|
154
|
-
|
|
155
|
-
<div class="tradeoffs">
|
|
156
|
-
<div class="row head">
|
|
157
|
-
<div class="cell">Pro</div>
|
|
158
|
-
<div class="cell">Con</div>
|
|
159
|
-
</div>
|
|
160
|
-
<div class="row">
|
|
161
|
-
<div class="cell pro">leading / trailing / maxWait handled for us</div>
|
|
162
|
-
<div class="cell con">New runtime dependency to audit and update</div>
|
|
163
|
-
</div>
|
|
164
|
-
<div class="row">
|
|
165
|
-
<div class="cell pro">Callback form skips intermediate re-renders</div>
|
|
166
|
-
<div class="cell con">Uncontrolled input diverges from Acme form patterns</div>
|
|
167
|
-
</div>
|
|
168
|
-
<div class="row">
|
|
169
|
-
<div class="cell pro">Well-tested edge cases (unmount, flush, cancel)</div>
|
|
170
|
-
<div class="cell con">~1.4 kb gzipped for something we could own</div>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
|
|
174
|
-
<div class="chips">
|
|
175
|
-
<span class="chip">Bundle impact: <strong>+1.4 kb</strong></span>
|
|
176
|
-
<span class="chip">Testability: <strong>high</strong></span>
|
|
177
|
-
<span class="chip">Reuse: <strong>high</strong></span>
|
|
178
|
-
<span class="chip">SSR safe: <strong>yes</strong></span>
|
|
179
|
-
</div>
|
|
180
|
-
</article>
|
|
181
|
-
|
|
182
|
-
</section>
|
|
183
|
-
|
|
184
|
-
<!-- ============================================================= -->
|
|
185
|
-
|
|
186
|
-
<aside class="reco">
|
|
187
|
-
<h2>Recommendation</h2>
|
|
188
|
-
<p>
|
|
189
|
-
Go with <strong>approach 02, the custom <code>useDebounce</code> hook</strong>.
|
|
190
|
-
Acme already has three places that hand-roll the inline pattern
|
|
191
|
-
(task filter, command palette, member picker), so extracting one
|
|
192
|
-
shared hook removes duplication without taking on a new dependency.
|
|
193
|
-
</p>
|
|
194
|
-
<p>
|
|
195
|
-
Revisit approach 03 only if we later need <code>maxWait</code> or
|
|
196
|
-
<code>flush()</code> semantics — the library earns its bundle cost
|
|
197
|
-
once the requirements outgrow a ten-line hook.
|
|
198
|
-
</p>
|
|
199
|
-
</aside>
|
|
200
|
-
|
|
201
|
-
</div>
|
|
202
|
-
|
|
203
|
-
<style>
|
|
204
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
205
|
-
.page {
|
|
206
|
-
max-width: 1360px;
|
|
207
|
-
margin: 0 auto;
|
|
208
|
-
}
|
|
209
|
-
/* ---------- header ---------- */
|
|
210
|
-
|
|
211
|
-
header.page-head {
|
|
212
|
-
margin-bottom: 48px;
|
|
213
|
-
max-width: 760px;
|
|
214
|
-
}
|
|
215
|
-
.eyebrow {
|
|
216
|
-
font-size: 12px;
|
|
217
|
-
letter-spacing: 0.08em;
|
|
218
|
-
text-transform: uppercase;
|
|
219
|
-
color: var(--gray-500);
|
|
220
|
-
margin-bottom: 12px;
|
|
221
|
-
}
|
|
222
|
-
h1 {
|
|
223
|
-
font-family: var(--serif);
|
|
224
|
-
font-weight: 500;
|
|
225
|
-
font-size: 38px;
|
|
226
|
-
line-height: 1.15;
|
|
227
|
-
color: var(--slate);
|
|
228
|
-
margin-bottom: 18px;
|
|
229
|
-
letter-spacing: -0.01em;
|
|
230
|
-
}
|
|
231
|
-
.prompt-box {
|
|
232
|
-
background: var(--gray-150);
|
|
233
|
-
border: 1.5px solid var(--gray-300);
|
|
234
|
-
border-radius: 12px;
|
|
235
|
-
padding: 16px 20px;
|
|
236
|
-
font-size: 14.5px;
|
|
237
|
-
color: var(--gray-700);
|
|
238
|
-
}
|
|
239
|
-
.prompt-box .label {
|
|
240
|
-
font-family: var(--mono);
|
|
241
|
-
font-size: 11px;
|
|
242
|
-
text-transform: uppercase;
|
|
243
|
-
letter-spacing: 0.06em;
|
|
244
|
-
color: var(--gray-500);
|
|
245
|
-
display: block;
|
|
246
|
-
margin-bottom: 6px;
|
|
247
|
-
}
|
|
248
|
-
/* ---------- approach grid ---------- */
|
|
249
|
-
|
|
250
|
-
.approaches {
|
|
251
|
-
display: grid;
|
|
252
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
253
|
-
gap: 28px;
|
|
254
|
-
margin-bottom: 56px;
|
|
255
|
-
}
|
|
256
|
-
@media (max-width: 1100px) {
|
|
257
|
-
.approaches { grid-template-columns: 1fr; }
|
|
258
|
-
}
|
|
259
|
-
.approach {
|
|
260
|
-
background: var(--white);
|
|
261
|
-
border: 1.5px solid var(--gray-300);
|
|
262
|
-
border-radius: 12px;
|
|
263
|
-
padding: 24px;
|
|
264
|
-
display: flex;
|
|
265
|
-
flex-direction: column;
|
|
266
|
-
gap: 20px;
|
|
267
|
-
}
|
|
268
|
-
.approach-head h2 {
|
|
269
|
-
font-family: var(--serif);
|
|
270
|
-
font-weight: 500;
|
|
271
|
-
font-size: 21px;
|
|
272
|
-
color: var(--slate);
|
|
273
|
-
margin-bottom: 6px;
|
|
274
|
-
}
|
|
275
|
-
.approach-head .num {
|
|
276
|
-
display: inline-block;
|
|
277
|
-
font-family: var(--mono);
|
|
278
|
-
font-size: 12px;
|
|
279
|
-
background: var(--oat);
|
|
280
|
-
color: var(--slate);
|
|
281
|
-
padding: 2px 8px;
|
|
282
|
-
border-radius: 8px;
|
|
283
|
-
margin-right: 8px;
|
|
284
|
-
vertical-align: 3px;
|
|
285
|
-
}
|
|
286
|
-
.approach-head p {
|
|
287
|
-
font-size: 14px;
|
|
288
|
-
color: var(--gray-500);
|
|
289
|
-
}
|
|
290
|
-
/* ---------- code panel ---------- */
|
|
291
|
-
|
|
292
|
-
.code {
|
|
293
|
-
background: var(--slate);
|
|
294
|
-
border-radius: 12px;
|
|
295
|
-
padding: 18px 20px;
|
|
296
|
-
overflow-x: auto;
|
|
297
|
-
}
|
|
298
|
-
.code pre {
|
|
299
|
-
font-family: var(--mono);
|
|
300
|
-
font-size: 12.5px;
|
|
301
|
-
line-height: 1.65;
|
|
302
|
-
color: #E8E6DE;
|
|
303
|
-
white-space: pre;
|
|
304
|
-
}
|
|
305
|
-
.code .kw { color: var(--clay); }
|
|
306
|
-
/* keywords */
|
|
307
|
-
.code .str { color: var(--olive); }
|
|
308
|
-
/* strings */
|
|
309
|
-
.code .cm { color: var(--gray-500); }
|
|
310
|
-
/* comments */
|
|
311
|
-
.code .fn { color: #C9B98A; }
|
|
312
|
-
/* identifiers, subtle warm */
|
|
313
|
-
|
|
314
|
-
/* ---------- tradeoffs table ---------- */
|
|
315
|
-
|
|
316
|
-
.tradeoffs {
|
|
317
|
-
border: 1.5px solid var(--gray-300);
|
|
318
|
-
border-radius: 8px;
|
|
319
|
-
overflow: hidden;
|
|
320
|
-
font-size: 13px;
|
|
321
|
-
}
|
|
322
|
-
.tradeoffs .row {
|
|
323
|
-
display: grid;
|
|
324
|
-
grid-template-columns: 1fr 1fr;
|
|
325
|
-
}
|
|
326
|
-
.tradeoffs .row + .row {
|
|
327
|
-
border-top: 1.5px solid var(--gray-300);
|
|
328
|
-
}
|
|
329
|
-
.tradeoffs .cell {
|
|
330
|
-
padding: 10px 14px;
|
|
331
|
-
}
|
|
332
|
-
.tradeoffs .cell:first-child {
|
|
333
|
-
border-right: 1.5px solid var(--gray-300);
|
|
334
|
-
}
|
|
335
|
-
.tradeoffs .head {
|
|
336
|
-
background: var(--gray-150);
|
|
337
|
-
font-weight: 600;
|
|
338
|
-
color: var(--slate);
|
|
339
|
-
font-size: 12px;
|
|
340
|
-
text-transform: uppercase;
|
|
341
|
-
letter-spacing: 0.04em;
|
|
342
|
-
}
|
|
343
|
-
.tradeoffs .pro::before,
|
|
344
|
-
.tradeoffs .con::before {
|
|
345
|
-
content: '';
|
|
346
|
-
display: inline-block;
|
|
347
|
-
width: 6px;
|
|
348
|
-
height: 6px;
|
|
349
|
-
border-radius: 50%;
|
|
350
|
-
margin-right: 8px;
|
|
351
|
-
vertical-align: 2px;
|
|
352
|
-
}
|
|
353
|
-
.tradeoffs .pro::before { background: var(--olive); }
|
|
354
|
-
.tradeoffs .con::before { background: var(--clay); }
|
|
355
|
-
/* ---------- chip footer ---------- */
|
|
356
|
-
|
|
357
|
-
.chips {
|
|
358
|
-
display: flex;
|
|
359
|
-
flex-wrap: wrap;
|
|
360
|
-
gap: 8px;
|
|
361
|
-
}
|
|
362
|
-
.chip {
|
|
363
|
-
font-family: var(--mono);
|
|
364
|
-
font-size: 11.5px;
|
|
365
|
-
background: var(--gray-150);
|
|
366
|
-
border: 1.5px solid var(--gray-300);
|
|
367
|
-
color: var(--gray-700);
|
|
368
|
-
padding: 5px 10px;
|
|
369
|
-
border-radius: 8px;
|
|
370
|
-
white-space: nowrap;
|
|
371
|
-
}
|
|
372
|
-
.chip strong { color: var(--slate); font-weight: 600; }
|
|
373
|
-
/* ---------- recommendation ---------- */
|
|
374
|
-
|
|
375
|
-
.reco {
|
|
376
|
-
border-left: 4px solid var(--clay);
|
|
377
|
-
background: var(--white);
|
|
378
|
-
border-radius: 0 12px 12px 0;
|
|
379
|
-
padding: 24px 28px;
|
|
380
|
-
max-width: 860px;
|
|
381
|
-
}
|
|
382
|
-
.reco h2 {
|
|
383
|
-
font-family: var(--serif);
|
|
384
|
-
font-weight: 500;
|
|
385
|
-
font-size: 22px;
|
|
386
|
-
color: var(--slate);
|
|
387
|
-
margin-bottom: 10px;
|
|
388
|
-
}
|
|
389
|
-
.reco p {
|
|
390
|
-
font-size: 15px;
|
|
391
|
-
margin-bottom: 8px;
|
|
392
|
-
}
|
|
393
|
-
.reco code {
|
|
394
|
-
font-family: var(--mono);
|
|
395
|
-
font-size: 0.92em;
|
|
396
|
-
background: var(--gray-150);
|
|
397
|
-
padding: 1px 6px;
|
|
398
|
-
border-radius: 4px;
|
|
399
|
-
}
|
|
400
|
-
</style>
|