@a13xu/lucid 1.11.0 โ 1.12.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/build/index.js +1 -1
- package/build/tools/init.js +42 -1
- package/package.json +2 -1
- package/skills/lucid-audit/SKILL.md +53 -0
- package/skills/lucid-context/SKILL.md +35 -0
- package/skills/lucid-plan/SKILL.md +60 -0
- package/skills/lucid-security/SKILL.md +59 -0
- package/skills/lucid-webdev/SKILL.md +123 -0
package/build/index.js
CHANGED
|
@@ -54,7 +54,7 @@ else {
|
|
|
54
54
|
// ---------------------------------------------------------------------------
|
|
55
55
|
// MCP Server
|
|
56
56
|
// ---------------------------------------------------------------------------
|
|
57
|
-
const server = new Server({ name: "lucid", version: "1.
|
|
57
|
+
const server = new Server({ name: "lucid", version: "1.12.0" }, { capabilities: { tools: {} } });
|
|
58
58
|
// ---------------------------------------------------------------------------
|
|
59
59
|
// Tool definitions
|
|
60
60
|
// ---------------------------------------------------------------------------
|
package/build/tools/init.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolve, join } from "path";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
4
5
|
import { indexProject } from "../indexer/project.js";
|
|
5
6
|
import { saveAdminConfig, loadAdminConfig, isAdminConfigured, sendTestAlert, } from "../security/alerts.js";
|
|
6
7
|
export const InitProjectSchema = z.object({
|
|
@@ -118,6 +119,18 @@ export async function handleInitProject(stmts, input) {
|
|
|
118
119
|
else {
|
|
119
120
|
lines.push(`๐ Hook: ${hookResult.reason}`);
|
|
120
121
|
}
|
|
122
|
+
// โโ Skills โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
123
|
+
const skillsResult = installSkills(dir);
|
|
124
|
+
if (skillsResult.installed.length > 0) {
|
|
125
|
+
lines.push(`๐ Skills installed in .claude/skills/:`);
|
|
126
|
+
for (const s of skillsResult.installed) {
|
|
127
|
+
lines.push(` โข /${s}`);
|
|
128
|
+
}
|
|
129
|
+
lines.push(` Invoke with /<skill-name> in Claude Code.`);
|
|
130
|
+
}
|
|
131
|
+
else if (skillsResult.skipped.length > 0) {
|
|
132
|
+
lines.push(`๐ Skills: already installed (${skillsResult.skipped.length} skill(s))`);
|
|
133
|
+
}
|
|
121
134
|
// โโ CLAUDE.md injection โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
122
135
|
const injected = injectClaudeMdInstruction(dir);
|
|
123
136
|
if (injected) {
|
|
@@ -200,6 +213,34 @@ export async function handleInitProject(stmts, input) {
|
|
|
200
213
|
lines.push(`Use recall() to query accumulated project knowledge.`);
|
|
201
214
|
return lines.join("\n");
|
|
202
215
|
}
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Instaleazฤ Lucid skills รฎn .claude/skills/ al proiectului
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
const PACKAGE_ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "../..");
|
|
220
|
+
function installSkills(projectDir) {
|
|
221
|
+
const skillsSource = join(PACKAGE_ROOT, "skills");
|
|
222
|
+
const result = { installed: [], skipped: [] };
|
|
223
|
+
if (!existsSync(skillsSource))
|
|
224
|
+
return result;
|
|
225
|
+
const skillDirs = readdirSync(skillsSource, { withFileTypes: true })
|
|
226
|
+
.filter((d) => d.isDirectory())
|
|
227
|
+
.map((d) => d.name);
|
|
228
|
+
for (const skillName of skillDirs) {
|
|
229
|
+
const srcSkillMd = join(skillsSource, skillName, "SKILL.md");
|
|
230
|
+
if (!existsSync(srcSkillMd))
|
|
231
|
+
continue;
|
|
232
|
+
const destDir = join(projectDir, ".claude", "skills", skillName);
|
|
233
|
+
const destFile = join(destDir, "SKILL.md");
|
|
234
|
+
if (existsSync(destFile)) {
|
|
235
|
+
result.skipped.push(skillName);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
mkdirSync(destDir, { recursive: true });
|
|
239
|
+
writeFileSync(destFile, readFileSync(srcSkillMd, "utf-8"), "utf-8");
|
|
240
|
+
result.installed.push(skillName);
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
203
244
|
function buildChannelSummary(cfg) {
|
|
204
245
|
const channels = [];
|
|
205
246
|
if (cfg.adminEmail && cfg.smtpHost)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a13xu/lucid",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Token-efficient memory, code indexing, and validation for Claude Code agents โ SQLite + FTS5, TF-IDF + Qdrant retrieval, AST skeleton pruning, diff-aware context, Logic Guardian drift detection",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"build/**/*.js",
|
|
11
11
|
"build/**/*.d.ts",
|
|
12
|
+
"skills/**/*.md",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lucid-audit
|
|
3
|
+
description: Run after writing or modifying code โ validates logic correctness (Logic Guardian 5 passes) and code quality (25 Golden Rules) before marking work as done.
|
|
4
|
+
argument-hint: "[file path or 'all']"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Lucid Code Audit
|
|
8
|
+
|
|
9
|
+
Run this skill BEFORE marking any implementation as complete. It runs two complementary validators:
|
|
10
|
+
|
|
11
|
+
- **Logic Guardian** (`validate_file`) โ detects LLM drift: logic inversions, null propagation, off-by-one, copy-paste mistakes
|
|
12
|
+
- **Code Quality Guard** (`check_code_quality`) โ detects structural issues: file/function size, vague naming, deep nesting, prop explosion, inline styles
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
|
|
16
|
+
### 1. Validate logic correctness
|
|
17
|
+
```
|
|
18
|
+
validate_file(path="<file you wrote or modified>")
|
|
19
|
+
```
|
|
20
|
+
Fix any ๐ด CRITICAL issues before continuing.
|
|
21
|
+
|
|
22
|
+
### 2. Validate code quality
|
|
23
|
+
```
|
|
24
|
+
check_code_quality(path="<same file>")
|
|
25
|
+
```
|
|
26
|
+
Fix any ๐ด HIGH severity issues. Address ๐ MEDIUM where practical.
|
|
27
|
+
|
|
28
|
+
### 3. If unsure about a snippet before writing it to disk
|
|
29
|
+
```
|
|
30
|
+
check_drift(code="<your code>", language="typescript")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 4. Get the full 5-pass mental checklist
|
|
34
|
+
```
|
|
35
|
+
get_checklist()
|
|
36
|
+
```
|
|
37
|
+
Use this when changes are complex or involve critical business logic.
|
|
38
|
+
|
|
39
|
+
## Severity guide
|
|
40
|
+
|
|
41
|
+
| Icon | Level | Action |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| ๐ด | Critical/High | Fix immediately โ do not ship |
|
|
44
|
+
| ๐ | Medium/Warning | Fix if not risky refactor |
|
|
45
|
+
| ๐ต | Low/Info | Note for future cleanup |
|
|
46
|
+
|
|
47
|
+
## What each tool catches
|
|
48
|
+
|
|
49
|
+
| Tool | Catches |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `validate_file` | Logic inversions, silent exceptions, null propagation, type confusion, stale closures |
|
|
52
|
+
| `check_code_quality` | Files >500 lines, functions >100 lines, vague names, nesting >4 levels, dead code, React/Vue component anti-patterns |
|
|
53
|
+
| `check_drift` | Same as validate_file but on inline snippets โ use before writing |
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lucid-context
|
|
3
|
+
description: Use before starting any coding task โ retrieves minimal relevant context via Lucid's TF-IDF retrieval, then rewards or penalizes based on usefulness.
|
|
4
|
+
argument-hint: "[what you are working on]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Lucid Context Retrieval
|
|
8
|
+
|
|
9
|
+
Use this skill at the START of every coding task to get only the relevant files instead of reading the whole codebase.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. **Call `get_context`** with a concise description of what you're working on:
|
|
14
|
+
```
|
|
15
|
+
get_context(query="<what you are working on>", maxTokens=4000)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
2. **Review the returned skeletons/files.** If they are relevant โ call `reward()`. If they missed important files โ call `penalize()` and note what was missing.
|
|
19
|
+
|
|
20
|
+
3. **Start coding** using the context you received.
|
|
21
|
+
|
|
22
|
+
## Tips
|
|
23
|
+
|
|
24
|
+
- Use `dirs` to narrow scope: `get_context(query="...", dirs=["src/api"])`
|
|
25
|
+
- Use `get_recent(hours=2)` after a git pull or when resuming a session to see what changed
|
|
26
|
+
- Use `grep_code(pattern="...")` to locate specific function usages without reading full files
|
|
27
|
+
- After finishing, call `sync_file(path="<modified file>")` to keep the knowledge graph current
|
|
28
|
+
|
|
29
|
+
## When to reward vs penalize
|
|
30
|
+
|
|
31
|
+
| Situation | Action |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Context included the files that helped solve the task | `reward()` |
|
|
34
|
+
| Context missed key files you had to find manually | `penalize(note="missed: src/utils/auth.ts")` |
|
|
35
|
+
| Context was partially useful | No action needed |
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lucid-plan
|
|
3
|
+
description: Create and track an implementation plan before writing any code โ use Lucid's planning tools to define user story, ordered tasks, and test criteria.
|
|
4
|
+
argument-hint: "[feature or task description]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Lucid Planning Workflow
|
|
8
|
+
|
|
9
|
+
Use this skill BEFORE writing code for any non-trivial feature. Plans are persisted in the Lucid DB and survive session restarts.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
### 1. Create the plan
|
|
14
|
+
```
|
|
15
|
+
plan_create(
|
|
16
|
+
title="<short title>",
|
|
17
|
+
description="<what this plan accomplishes>",
|
|
18
|
+
user_story="As a <user>, I want <goal>, so that <benefit>.",
|
|
19
|
+
tasks=[
|
|
20
|
+
{ title: "Task 1", description: "...", test_criteria: "How to verify done" },
|
|
21
|
+
{ title: "Task 2", description: "...", test_criteria: "..." },
|
|
22
|
+
]
|
|
23
|
+
)
|
|
24
|
+
```
|
|
25
|
+
Returns a `plan_id` and task IDs (format: `planId * 100 + sequence`).
|
|
26
|
+
|
|
27
|
+
### 2. Work through tasks
|
|
28
|
+
For each task, mark it in progress when you start:
|
|
29
|
+
```
|
|
30
|
+
plan_update_task(task_id=101, status="in_progress")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
When done, mark it complete (optionally add a note):
|
|
34
|
+
```
|
|
35
|
+
plan_update_task(task_id=101, status="done", note="Used useFetch instead of axios")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Plan auto-completes when all tasks reach `done`.
|
|
39
|
+
|
|
40
|
+
### 3. Resume a session โ check plan status
|
|
41
|
+
```
|
|
42
|
+
plan_list() # see all active plans
|
|
43
|
+
plan_get(plan_id=1) # see full details + task status
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Task statuses
|
|
47
|
+
|
|
48
|
+
| Status | When to use |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `pending` | Not started yet |
|
|
51
|
+
| `in_progress` | Currently working on it |
|
|
52
|
+
| `done` | Completed and verified |
|
|
53
|
+
| `blocked` | Waiting on external dependency |
|
|
54
|
+
|
|
55
|
+
## Tips
|
|
56
|
+
|
|
57
|
+
- Define `test_criteria` clearly โ it becomes your acceptance test
|
|
58
|
+
- Use `plan_get` when resuming to quickly re-orient yourself
|
|
59
|
+
- Keep tasks small (1โ4 hours each); use more tasks rather than fewer
|
|
60
|
+
- Notes are append-only โ use them to document decisions made during implementation
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lucid-security
|
|
3
|
+
description: Run a full security review on a file or snippet โ combines web vulnerability scanning (XSS, injection, secrets) with LLM drift detection before shipping code.
|
|
4
|
+
argument-hint: "[file path or paste code]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Lucid Security Review
|
|
8
|
+
|
|
9
|
+
Run this skill before shipping any code that handles user input, authentication, file access, or external data.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
### 1. Scan for web security vulnerabilities
|
|
14
|
+
```
|
|
15
|
+
security_scan(
|
|
16
|
+
code="<file contents or snippet>",
|
|
17
|
+
language="typescript", # javascript | typescript | html | vue
|
|
18
|
+
context="backend" # frontend | backend | api
|
|
19
|
+
)
|
|
20
|
+
```
|
|
21
|
+
Detects: XSS vectors, eval/new Function, SQL injection via string concat, hardcoded secrets/keys, open redirects, prototype pollution, path traversal, insecure CORS.
|
|
22
|
+
|
|
23
|
+
### 2. Scan for logic errors (LLM drift)
|
|
24
|
+
```
|
|
25
|
+
validate_file(path="<file path>")
|
|
26
|
+
```
|
|
27
|
+
Catches security-adjacent logic bugs: wrong condition direction, silent exception swallowing, null propagation into auth checks.
|
|
28
|
+
|
|
29
|
+
### 3. For frontend components โ audit accessibility too
|
|
30
|
+
```
|
|
31
|
+
accessibility_audit(code="<template or JSX>", wcag_level="AA", framework="vue")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Severity guide
|
|
35
|
+
|
|
36
|
+
| Icon | Severity | Action |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| ๐ด Critical | XSS, eval, hardcoded secret, SQL injection | Fix before any commit |
|
|
39
|
+
| ๐ High | Open redirect, path traversal, prototype pollution | Fix before merge |
|
|
40
|
+
| ๐ก Medium | Wildcard CORS, missing CSRF protection | Fix before production |
|
|
41
|
+
| ๐ต Low | console.log, minor info leakage | Fix when convenient |
|
|
42
|
+
|
|
43
|
+
## Common patterns to watch
|
|
44
|
+
|
|
45
|
+
| Pattern | Risk |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `element.innerHTML = userInput` | XSS โ use `textContent` or DOMPurify |
|
|
48
|
+
| `eval(...)` / `new Function(...)` | Code injection |
|
|
49
|
+
| `const key = "sk-abc123..."` | Hardcoded secret โ move to env var |
|
|
50
|
+
| `res.redirect(req.query.url)` | Open redirect โ validate against allowlist |
|
|
51
|
+
| `readFile(req.params.filename)` | Path traversal โ use `path.resolve` + bounds check |
|
|
52
|
+
| `Access-Control-Allow-Origin: *` | Overly permissive CORS |
|
|
53
|
+
|
|
54
|
+
## Note
|
|
55
|
+
|
|
56
|
+
Static scanning finds patterns, not all vulnerabilities. Complement with:
|
|
57
|
+
- Manual code review for business logic flaws
|
|
58
|
+
- DAST (dynamic testing) for runtime issues
|
|
59
|
+
- Dependency audit: `npm audit` / `pip-audit`
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lucid-webdev
|
|
3
|
+
description: Web development code generation tools โ generate components, pages, SEO meta, API clients, tests, layouts, design tokens, and analyze accessibility and performance.
|
|
4
|
+
argument-hint: "[component | page | seo | a11y | api | test | layout | security | tokens | perf]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Lucid Web Dev Tools
|
|
8
|
+
|
|
9
|
+
10 tools for common web development tasks. Pick the one that matches what you need:
|
|
10
|
+
|
|
11
|
+
## Component & Page Generation
|
|
12
|
+
|
|
13
|
+
### Generate a component
|
|
14
|
+
```
|
|
15
|
+
generate_component(
|
|
16
|
+
description="user profile card with avatar and edit button",
|
|
17
|
+
framework="vue", # react | vue | nuxt
|
|
18
|
+
styling="tailwind", # tailwind | css-modules | none
|
|
19
|
+
typescript=true
|
|
20
|
+
)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Generate a page scaffold
|
|
24
|
+
```
|
|
25
|
+
scaffold_page(
|
|
26
|
+
page_name="ProductDetail",
|
|
27
|
+
framework="nuxt", # nuxt | next | vue
|
|
28
|
+
sections=["hero", "specs", "reviews", "cta"],
|
|
29
|
+
seo_title="Product Detail"
|
|
30
|
+
)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## SEO & Accessibility
|
|
34
|
+
|
|
35
|
+
### Generate SEO metadata
|
|
36
|
+
```
|
|
37
|
+
seo_meta(
|
|
38
|
+
title="Buy Widgets โ Best Price",
|
|
39
|
+
description="Shop our range of premium widgets with free delivery.",
|
|
40
|
+
keywords=["widgets", "buy widgets", "widget shop"],
|
|
41
|
+
page_type="product", # article | product | landing | home
|
|
42
|
+
url="https://example.com/widgets",
|
|
43
|
+
image_url="https://example.com/og/widgets.jpg"
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
Returns: HTML meta tags + Open Graph + Twitter Card + JSON-LD structured data.
|
|
47
|
+
|
|
48
|
+
### Audit accessibility (WCAG)
|
|
49
|
+
```
|
|
50
|
+
accessibility_audit(
|
|
51
|
+
code="<your HTML/JSX/Vue snippet>",
|
|
52
|
+
wcag_level="AA", # A | AA | AAA
|
|
53
|
+
framework="vue" # html | jsx | vue
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
Returns: violations with severity (critical/warning/info), WCAG criterion, and corrected code.
|
|
57
|
+
|
|
58
|
+
## API & Testing
|
|
59
|
+
|
|
60
|
+
### Generate a typed API client
|
|
61
|
+
```
|
|
62
|
+
api_client(
|
|
63
|
+
endpoint="/users/:id",
|
|
64
|
+
method="GET", # GET | POST | PUT | PATCH | DELETE
|
|
65
|
+
response_schema="{ id: string; name: string; email: string }",
|
|
66
|
+
auth="bearer", # bearer | cookie | apikey | none
|
|
67
|
+
base_url_var="NEXT_PUBLIC_API_URL"
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Generate tests
|
|
72
|
+
```
|
|
73
|
+
test_generator(
|
|
74
|
+
code="<your function or component source>",
|
|
75
|
+
test_framework="vitest", # vitest | jest | playwright
|
|
76
|
+
test_type="unit", # unit | integration | e2e
|
|
77
|
+
component_framework="vue" # vue | react | none
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Layout & Design
|
|
82
|
+
|
|
83
|
+
### Generate a responsive layout
|
|
84
|
+
```
|
|
85
|
+
responsive_layout(
|
|
86
|
+
description="sidebar left 260px, main content, right panel 240px",
|
|
87
|
+
framework="tailwind", # tailwind | css-grid | flexbox
|
|
88
|
+
breakpoints=["mobile", "tablet", "desktop"],
|
|
89
|
+
container="sidebar" # full | centered | sidebar
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Generate design tokens
|
|
94
|
+
```
|
|
95
|
+
design_tokens(
|
|
96
|
+
brand_name="Acme",
|
|
97
|
+
primary_color="#6366F1", # hex or name (blue, green, etc.)
|
|
98
|
+
mood="minimal", # minimal | bold | playful | corporate
|
|
99
|
+
output_format="css-variables" # css-variables | tailwind-config | json
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Security & Performance
|
|
104
|
+
|
|
105
|
+
### Scan for security vulnerabilities
|
|
106
|
+
```
|
|
107
|
+
security_scan(
|
|
108
|
+
code="<your code snippet>",
|
|
109
|
+
language="typescript", # javascript | typescript | html | vue
|
|
110
|
+
context="frontend" # frontend | backend | api
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
Detects: XSS, eval/injection, hardcoded secrets, SQL injection, open redirects, CORS issues.
|
|
114
|
+
|
|
115
|
+
### Analyze Core Web Vitals issues
|
|
116
|
+
```
|
|
117
|
+
perf_hints(
|
|
118
|
+
code="<your component or page source>",
|
|
119
|
+
framework="vue", # react | vue | nuxt | vanilla
|
|
120
|
+
context="page" # component | page | layout
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
Detects: missing image dimensions (CLS), render-blocking scripts (FCP), fetch-in-render (TTFB), heavy click handlers (INP), missing useMemo/computed.
|