safeword 0.2.4 → 0.2.5
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/dist/check-3NGQ4NR5.js +129 -0
- package/dist/check-3NGQ4NR5.js.map +1 -0
- package/dist/chunk-2XWIUEQK.js +190 -0
- package/dist/chunk-2XWIUEQK.js.map +1 -0
- package/dist/chunk-GZRQL3SX.js +146 -0
- package/dist/chunk-GZRQL3SX.js.map +1 -0
- package/dist/chunk-ORQHKDT2.js +10 -0
- package/dist/chunk-ORQHKDT2.js.map +1 -0
- package/dist/chunk-W66Z3C5H.js +21 -0
- package/dist/chunk-W66Z3C5H.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +34 -0
- package/dist/cli.js.map +1 -0
- package/dist/diff-Y6QTAW4O.js +166 -0
- package/dist/diff-Y6QTAW4O.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/reset-3ACTIYYE.js +143 -0
- package/dist/reset-3ACTIYYE.js.map +1 -0
- package/dist/setup-RR4M334C.js +266 -0
- package/dist/setup-RR4M334C.js.map +1 -0
- package/dist/upgrade-6AR3DHUV.js +134 -0
- package/dist/upgrade-6AR3DHUV.js.map +1 -0
- package/package.json +44 -19
- package/{.safeword → templates}/hooks/agents-md-check.sh +0 -0
- package/{.safeword → templates}/hooks/post-tool.sh +0 -0
- package/{.safeword → templates}/hooks/pre-commit.sh +0 -0
- package/.claude/commands/arch-review.md +0 -32
- package/.claude/commands/lint.md +0 -6
- package/.claude/commands/quality-review.md +0 -13
- package/.claude/commands/setup-linting.md +0 -6
- package/.claude/hooks/auto-lint.sh +0 -6
- package/.claude/hooks/auto-quality-review.sh +0 -170
- package/.claude/hooks/check-linting-sync.sh +0 -17
- package/.claude/hooks/inject-timestamp.sh +0 -6
- package/.claude/hooks/question-protocol.sh +0 -12
- package/.claude/hooks/run-linters.sh +0 -8
- package/.claude/hooks/run-quality-review.sh +0 -76
- package/.claude/hooks/version-check.sh +0 -10
- package/.claude/mcp/README.md +0 -96
- package/.claude/mcp/arcade.sample.json +0 -9
- package/.claude/mcp/context7.sample.json +0 -7
- package/.claude/mcp/playwright.sample.json +0 -7
- package/.claude/settings.json +0 -62
- package/.claude/skills/quality-reviewer/SKILL.md +0 -190
- package/.claude/skills/safeword-quality-reviewer/SKILL.md +0 -13
- package/.env.arcade.example +0 -4
- package/.env.example +0 -11
- package/.gitmodules +0 -4
- package/.safeword/SAFEWORD.md +0 -33
- package/.safeword/eslint/eslint-base.mjs +0 -101
- package/.safeword/guides/architecture-guide.md +0 -404
- package/.safeword/guides/code-philosophy.md +0 -174
- package/.safeword/guides/context-files-guide.md +0 -405
- package/.safeword/guides/data-architecture-guide.md +0 -183
- package/.safeword/guides/design-doc-guide.md +0 -165
- package/.safeword/guides/learning-extraction.md +0 -515
- package/.safeword/guides/llm-instruction-design.md +0 -239
- package/.safeword/guides/llm-prompting.md +0 -95
- package/.safeword/guides/tdd-best-practices.md +0 -570
- package/.safeword/guides/test-definitions-guide.md +0 -243
- package/.safeword/guides/testing-methodology.md +0 -573
- package/.safeword/guides/user-story-guide.md +0 -237
- package/.safeword/guides/zombie-process-cleanup.md +0 -214
- package/.safeword/planning/002-user-story-quality-evaluation.md +0 -1840
- package/.safeword/planning/003-langsmith-eval-setup-prompt.md +0 -363
- package/.safeword/planning/004-llm-eval-test-cases.md +0 -3226
- package/.safeword/planning/005-architecture-enforcement-system.md +0 -169
- package/.safeword/planning/006-reactive-fix-prevention-research.md +0 -135
- package/.safeword/planning/011-cli-ux-vision.md +0 -330
- package/.safeword/planning/012-project-structure-cleanup.md +0 -154
- package/.safeword/planning/README.md +0 -39
- package/.safeword/planning/automation-plan-v2.md +0 -1225
- package/.safeword/planning/automation-plan-v3.md +0 -1291
- package/.safeword/planning/automation-plan.md +0 -3058
- package/.safeword/planning/design/005-cli-implementation.md +0 -343
- package/.safeword/planning/design/013-cli-self-contained-templates.md +0 -596
- package/.safeword/planning/design/013a-eslint-plugin-suite.md +0 -256
- package/.safeword/planning/design/013b-implementation-snippets.md +0 -385
- package/.safeword/planning/design/013c-config-isolation-strategy.md +0 -242
- package/.safeword/planning/design/code-philosophy-improvements.md +0 -60
- package/.safeword/planning/mcp-analysis.md +0 -545
- package/.safeword/planning/phase2-subagents-vs-skills-analysis.md +0 -451
- package/.safeword/planning/settings-improvements.md +0 -970
- package/.safeword/planning/test-definitions/005-cli-implementation.md +0 -1301
- package/.safeword/planning/test-definitions/cli-self-contained-templates.md +0 -205
- package/.safeword/planning/user-stories/001-guides-review-user-stories.md +0 -1381
- package/.safeword/planning/user-stories/003-reactive-fix-prevention.md +0 -132
- package/.safeword/planning/user-stories/004-technical-constraints.md +0 -86
- package/.safeword/planning/user-stories/005-cli-implementation.md +0 -311
- package/.safeword/planning/user-stories/cli-self-contained-templates.md +0 -172
- package/.safeword/planning/versioned-distribution.md +0 -740
- package/.safeword/prompts/arch-review.md +0 -43
- package/.safeword/prompts/quality-review.md +0 -11
- package/.safeword/scripts/arch-review.sh +0 -235
- package/.safeword/scripts/check-linting-sync.sh +0 -58
- package/.safeword/scripts/setup-linting.sh +0 -559
- package/.safeword/templates/architecture-template.md +0 -136
- package/.safeword/templates/ci/architecture-check.yml +0 -79
- package/.safeword/templates/design-doc-template.md +0 -127
- package/.safeword/templates/test-definitions-feature.md +0 -100
- package/.safeword/templates/ticket-template.md +0 -74
- package/.safeword/templates/user-stories-template.md +0 -82
- package/.safeword/tickets/001-guides-review-user-stories.md +0 -83
- package/.safeword/tickets/002-architecture-enforcement.md +0 -211
- package/.safeword/tickets/003-reactive-fix-prevention.md +0 -57
- package/.safeword/tickets/004-technical-constraints-in-user-stories.md +0 -39
- package/.safeword/tickets/005-cli-implementation.md +0 -248
- package/.safeword/tickets/006-flesh-out-skills.md +0 -43
- package/.safeword/tickets/007-flesh-out-questioning.md +0 -44
- package/.safeword/tickets/008-upgrade-questioning.md +0 -58
- package/.safeword/tickets/009-naming-conventions.md +0 -41
- package/.safeword/tickets/010-safeword-md-cleanup.md +0 -34
- package/.safeword/tickets/011-cursor-setup.md +0 -86
- package/.safeword/tickets/README.md +0 -73
- package/.safeword/version +0 -1
- package/AGENTS.md +0 -59
- package/CLAUDE.md +0 -12
- package/README.md +0 -347
- package/docs/001-cli-implementation-plan.md +0 -856
- package/docs/elite-dx-implementation-plan.md +0 -1034
- package/framework/README.md +0 -131
- package/framework/mcp/README.md +0 -96
- package/framework/mcp/arcade.sample.json +0 -8
- package/framework/mcp/context7.sample.json +0 -6
- package/framework/mcp/playwright.sample.json +0 -6
- package/framework/scripts/arch-review.sh +0 -235
- package/framework/scripts/check-linting-sync.sh +0 -58
- package/framework/scripts/load-env.sh +0 -49
- package/framework/scripts/setup-claude.sh +0 -223
- package/framework/scripts/setup-linting.sh +0 -559
- package/framework/scripts/setup-quality.sh +0 -477
- package/framework/scripts/setup-safeword.sh +0 -550
- package/framework/templates/ci/architecture-check.yml +0 -78
- package/learnings/ai-sdk-v5-breaking-changes.md +0 -178
- package/learnings/e2e-test-zombie-processes.md +0 -231
- package/learnings/milkdown-crepe-editor-property.md +0 -96
- package/learnings/prosemirror-fragment-traversal.md +0 -119
- package/packages/cli/AGENTS.md +0 -1
- package/packages/cli/ARCHITECTURE.md +0 -279
- package/packages/cli/package.json +0 -51
- package/packages/cli/src/cli.ts +0 -63
- package/packages/cli/src/commands/check.ts +0 -166
- package/packages/cli/src/commands/diff.ts +0 -209
- package/packages/cli/src/commands/reset.ts +0 -190
- package/packages/cli/src/commands/setup.ts +0 -325
- package/packages/cli/src/commands/upgrade.ts +0 -163
- package/packages/cli/src/index.ts +0 -3
- package/packages/cli/src/templates/config.ts +0 -58
- package/packages/cli/src/templates/content.ts +0 -18
- package/packages/cli/src/templates/index.ts +0 -12
- package/packages/cli/src/utils/agents-md.ts +0 -66
- package/packages/cli/src/utils/fs.ts +0 -179
- package/packages/cli/src/utils/git.ts +0 -124
- package/packages/cli/src/utils/hooks.ts +0 -29
- package/packages/cli/src/utils/output.ts +0 -60
- package/packages/cli/src/utils/project-detector.test.ts +0 -185
- package/packages/cli/src/utils/project-detector.ts +0 -44
- package/packages/cli/src/utils/version.ts +0 -28
- package/packages/cli/src/version.ts +0 -6
- package/packages/cli/templates/SAFEWORD.md +0 -776
- package/packages/cli/templates/doc-templates/architecture-template.md +0 -136
- package/packages/cli/templates/doc-templates/design-doc-template.md +0 -134
- package/packages/cli/templates/doc-templates/test-definitions-feature.md +0 -131
- package/packages/cli/templates/doc-templates/ticket-template.md +0 -82
- package/packages/cli/templates/doc-templates/user-stories-template.md +0 -92
- package/packages/cli/templates/guides/architecture-guide.md +0 -423
- package/packages/cli/templates/guides/code-philosophy.md +0 -195
- package/packages/cli/templates/guides/context-files-guide.md +0 -457
- package/packages/cli/templates/guides/data-architecture-guide.md +0 -200
- package/packages/cli/templates/guides/design-doc-guide.md +0 -171
- package/packages/cli/templates/guides/learning-extraction.md +0 -552
- package/packages/cli/templates/guides/llm-instruction-design.md +0 -248
- package/packages/cli/templates/guides/llm-prompting.md +0 -102
- package/packages/cli/templates/guides/tdd-best-practices.md +0 -615
- package/packages/cli/templates/guides/test-definitions-guide.md +0 -334
- package/packages/cli/templates/guides/testing-methodology.md +0 -618
- package/packages/cli/templates/guides/user-story-guide.md +0 -256
- package/packages/cli/templates/guides/zombie-process-cleanup.md +0 -219
- package/packages/cli/templates/hooks/agents-md-check.sh +0 -27
- package/packages/cli/templates/hooks/post-tool.sh +0 -4
- package/packages/cli/templates/hooks/pre-commit.sh +0 -10
- package/packages/cli/templates/prompts/arch-review.md +0 -43
- package/packages/cli/templates/prompts/quality-review.md +0 -10
- package/packages/cli/templates/skills/safeword-quality-reviewer/SKILL.md +0 -207
- package/packages/cli/tests/commands/check.test.ts +0 -129
- package/packages/cli/tests/commands/cli.test.ts +0 -89
- package/packages/cli/tests/commands/diff.test.ts +0 -115
- package/packages/cli/tests/commands/reset.test.ts +0 -310
- package/packages/cli/tests/commands/self-healing.test.ts +0 -170
- package/packages/cli/tests/commands/setup-blocking.test.ts +0 -71
- package/packages/cli/tests/commands/setup-core.test.ts +0 -135
- package/packages/cli/tests/commands/setup-git.test.ts +0 -139
- package/packages/cli/tests/commands/setup-hooks.test.ts +0 -334
- package/packages/cli/tests/commands/setup-linting.test.ts +0 -189
- package/packages/cli/tests/commands/setup-noninteractive.test.ts +0 -80
- package/packages/cli/tests/commands/setup-templates.test.ts +0 -181
- package/packages/cli/tests/commands/upgrade.test.ts +0 -215
- package/packages/cli/tests/helpers.ts +0 -243
- package/packages/cli/tests/npm-package.test.ts +0 -83
- package/packages/cli/tests/technical-constraints.test.ts +0 -96
- package/packages/cli/tsconfig.json +0 -25
- package/packages/cli/tsup.config.ts +0 -11
- package/packages/cli/vitest.config.ts +0 -23
- package/promptfoo.yaml +0 -3270
- /package/{framework → templates}/SAFEWORD.md +0 -0
- /package/{packages/cli/templates → templates}/commands/arch-review.md +0 -0
- /package/{packages/cli/templates → templates}/commands/lint.md +0 -0
- /package/{packages/cli/templates → templates}/commands/quality-review.md +0 -0
- /package/{framework/templates → templates/doc-templates}/architecture-template.md +0 -0
- /package/{framework/templates → templates/doc-templates}/design-doc-template.md +0 -0
- /package/{framework/templates → templates/doc-templates}/test-definitions-feature.md +0 -0
- /package/{framework/templates → templates/doc-templates}/ticket-template.md +0 -0
- /package/{framework/templates → templates/doc-templates}/user-stories-template.md +0 -0
- /package/{framework → templates}/guides/architecture-guide.md +0 -0
- /package/{framework → templates}/guides/code-philosophy.md +0 -0
- /package/{framework → templates}/guides/context-files-guide.md +0 -0
- /package/{framework → templates}/guides/data-architecture-guide.md +0 -0
- /package/{framework → templates}/guides/design-doc-guide.md +0 -0
- /package/{framework → templates}/guides/learning-extraction.md +0 -0
- /package/{framework → templates}/guides/llm-instruction-design.md +0 -0
- /package/{framework → templates}/guides/llm-prompting.md +0 -0
- /package/{framework → templates}/guides/tdd-best-practices.md +0 -0
- /package/{framework → templates}/guides/test-definitions-guide.md +0 -0
- /package/{framework → templates}/guides/testing-methodology.md +0 -0
- /package/{framework → templates}/guides/user-story-guide.md +0 -0
- /package/{framework → templates}/guides/zombie-process-cleanup.md +0 -0
- /package/{packages/cli/templates → templates}/hooks/inject-timestamp.sh +0 -0
- /package/{packages/cli/templates → templates}/lib/common.sh +0 -0
- /package/{packages/cli/templates → templates}/lib/jq-fallback.sh +0 -0
- /package/{packages/cli/templates → templates}/markdownlint.jsonc +0 -0
- /package/{framework → templates}/prompts/arch-review.md +0 -0
- /package/{framework → templates}/prompts/quality-review.md +0 -0
- /package/{framework/skills/quality-reviewer → templates/skills/safeword-quality-reviewer}/SKILL.md +0 -0
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
# AI SDK v5 Breaking Changes
|
|
2
|
-
|
|
3
|
-
**Principle:** AI SDK v5 removed `input` and `setInput` from `useChat` hook. You must manage text input state locally.
|
|
4
|
-
|
|
5
|
-
## The Gotcha
|
|
6
|
-
|
|
7
|
-
What breaks when upgrading from AI SDK v4 to v5:
|
|
8
|
-
|
|
9
|
-
❌ **Bad (AI SDK v4 pattern):**
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import { useChat } from '@ai-sdk/react'
|
|
13
|
-
|
|
14
|
-
export function Chat() {
|
|
15
|
-
const { input, setInput, messages, sendMessage } = useChat({
|
|
16
|
-
api: '/api/chat'
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
// input = undefined, setInput = undefined in v5!
|
|
20
|
-
return <input value={input} onChange={(e) => setInput(e.target.value)} />
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
✅ **Good (AI SDK v5 pattern):**
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
import { useChat } from '@ai-sdk/react'
|
|
28
|
-
import { useState } from 'react'
|
|
29
|
-
|
|
30
|
-
export function Chat() {
|
|
31
|
-
// Manage input state locally
|
|
32
|
-
const [input, setInput] = useState('')
|
|
33
|
-
|
|
34
|
-
const { messages, sendMessage } = useChat({
|
|
35
|
-
api: '/api/chat'
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
return <input value={input} onChange={(e) => setInput(e.target.value)} />
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**Why it matters:**
|
|
43
|
-
|
|
44
|
-
- `!undefined` = `true` → buttons stay disabled when checking `!input`
|
|
45
|
-
- `setInput(value)` throws "setInput is not a function"
|
|
46
|
-
- Tests may pass (if using controlled component wrappers) while browser fails
|
|
47
|
-
|
|
48
|
-
## Breaking Changes in AI SDK v5
|
|
49
|
-
|
|
50
|
-
### Removed from useChat return value:
|
|
51
|
-
|
|
52
|
-
- ❌ `input` (string)
|
|
53
|
-
- ❌ `setInput` (function)
|
|
54
|
-
- ❌ `handleSubmit` (function)
|
|
55
|
-
- ❌ `handleInputChange` (function)
|
|
56
|
-
|
|
57
|
-
### Still available:
|
|
58
|
-
|
|
59
|
-
- ✅ `messages`
|
|
60
|
-
- ✅ `setMessages`
|
|
61
|
-
- ✅ `sendMessage`
|
|
62
|
-
- ✅ `status`
|
|
63
|
-
- ✅ `stop`
|
|
64
|
-
- ✅ `reload`
|
|
65
|
-
- ✅ `data` (for data stream)
|
|
66
|
-
|
|
67
|
-
### New architecture:
|
|
68
|
-
|
|
69
|
-
AI SDK v5 uses **transport-based design**. Input management is now the developer's responsibility.
|
|
70
|
-
|
|
71
|
-
## Migration Pattern
|
|
72
|
-
|
|
73
|
-
**Before (v4):**
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
const { input, setInput, handleSubmit } = useChat()
|
|
77
|
-
|
|
78
|
-
<form onSubmit={handleSubmit}>
|
|
79
|
-
<input value={input} onChange={handleInputChange} />
|
|
80
|
-
</form>
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**After (v5):**
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
const [input, setInput] = useState('')
|
|
87
|
-
const { sendMessage } = useChat()
|
|
88
|
-
|
|
89
|
-
const handleSubmit = (e: FormEvent) => {
|
|
90
|
-
e.preventDefault()
|
|
91
|
-
if (input.trim()) {
|
|
92
|
-
sendMessage({ text: input })
|
|
93
|
-
setInput('')
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
<form onSubmit={handleSubmit}>
|
|
98
|
-
<input
|
|
99
|
-
value={input}
|
|
100
|
-
onChange={(e) => setInput(e.target.value)}
|
|
101
|
-
/>
|
|
102
|
-
</form>
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Testing Trap
|
|
106
|
-
|
|
107
|
-
Integration tests using `useState` in a TestWrapper will pass even if production code is broken:
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
// Test (passes even with broken useChat usage)
|
|
111
|
-
function TestWrapper() {
|
|
112
|
-
const [input, setInput] = useState('') // Local state works
|
|
113
|
-
return <MultimodalInput input={input} setInput={setInput} />
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Production (broken - input/setInput are undefined)
|
|
117
|
-
function Chat() {
|
|
118
|
-
const { input, setInput } = useChat() // undefined in v5!
|
|
119
|
-
return <MultimodalInput input={input} setInput={setInput} />
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**Solution:** Tests should verify the actual integration pattern, not just component behavior. Or add E2E tests that exercise the full Chat → useChat → MultimodalInput chain.
|
|
124
|
-
|
|
125
|
-
## Symptoms
|
|
126
|
-
|
|
127
|
-
If you see these symptoms, check if you're destructuring removed properties:
|
|
128
|
-
|
|
129
|
-
1. **Button stays disabled when typing** - `!undefined` = `true`
|
|
130
|
-
2. **Console error: "X is not a function"** - `setInput` doesn't exist
|
|
131
|
-
3. **Input state is undefined** - Check console: `input: undefined`
|
|
132
|
-
4. **Tests pass but browser fails** - TestWrapper uses local state, production uses broken useChat
|
|
133
|
-
|
|
134
|
-
## Debug Strategy
|
|
135
|
-
|
|
136
|
-
1. Add logging to see what useChat returns:
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
const chatHelpers = useChat({ ... })
|
|
140
|
-
console.log('useChat returned:', Object.keys(chatHelpers))
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
2. Check if input/setInput exist:
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
console.log('input:', input, 'setInput:', typeof setInput);
|
|
147
|
-
// v5: input: undefined setInput: undefined
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
3. Verify AI SDK version:
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
grep '"ai":' package.json
|
|
154
|
-
# "ai": "5.0.87" = v5 (breaking changes)
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## References
|
|
158
|
-
|
|
159
|
-
- AI SDK v5 docs: https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat
|
|
160
|
-
- Migration guide: https://ai-sdk.dev/docs/ai-sdk-ui/migration-guide
|
|
161
|
-
- Breaking change announcement: "The useChat API has been significantly updated in AI SDK 5.0"
|
|
162
|
-
|
|
163
|
-
## Real-World Example
|
|
164
|
-
|
|
165
|
-
**Project:** Chat application with send button
|
|
166
|
-
**Bug:** Button stayed disabled when typing
|
|
167
|
-
**Root cause:** Destructuring `input` and `setInput` from useChat (both undefined in v5)
|
|
168
|
-
**Fix:** Add `const [input, setInput] = useState('')` in Chat component
|
|
169
|
-
**Commit:** See project history for detailed investigation
|
|
170
|
-
|
|
171
|
-
## Prevention
|
|
172
|
-
|
|
173
|
-
**When upgrading to AI SDK v5:**
|
|
174
|
-
|
|
175
|
-
1. Search codebase for `useChat` usage: `grep -r "useChat" --include="*.tsx"`
|
|
176
|
-
2. Check each usage for destructuring `input`, `setInput`, `handleSubmit`, `handleInputChange`
|
|
177
|
-
3. Replace with local state management
|
|
178
|
-
4. Test in actual browser (integration tests may give false confidence)
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
# E2E Test Zombie Processes
|
|
2
|
-
|
|
3
|
-
**Principle:** E2E tests that spawn dev servers can create zombie processes if run in parallel. Always enforce sequential execution and provide clear cleanup instructions.
|
|
4
|
-
|
|
5
|
-
## The Gotcha
|
|
6
|
-
|
|
7
|
-
When E2E test frameworks (Playwright, Cypress) start dev servers via `webServer` config, running multiple test commands in parallel creates competing server instances that fight for the same port. This leads to:
|
|
8
|
-
|
|
9
|
-
- Port conflicts (EADDRINUSE)
|
|
10
|
-
- Orphaned processes that never terminate
|
|
11
|
-
- Tests hanging indefinitely during retry phase
|
|
12
|
-
- Accumulating zombie processes across multiple test runs
|
|
13
|
-
|
|
14
|
-
❌ **Bad:** Running multiple test commands simultaneously
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
# Terminal 1
|
|
18
|
-
pnpm test &
|
|
19
|
-
|
|
20
|
-
# Terminal 2 (before Terminal 1 completes)
|
|
21
|
-
pnpm test &
|
|
22
|
-
|
|
23
|
-
# Result: 2 webServers competing for port 3000
|
|
24
|
-
# Both hang, neither completes, processes accumulate
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
✅ **Good:** Enforce sequential execution
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
# Terminal 1
|
|
31
|
-
pnpm test
|
|
32
|
-
|
|
33
|
-
# Wait for completion...
|
|
34
|
-
|
|
35
|
-
# Terminal 2 (only after Terminal 1 finishes)
|
|
36
|
-
pnpm test
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**Why it matters:**
|
|
40
|
-
|
|
41
|
-
- Users don't realize background processes are still running
|
|
42
|
-
- Each retry attempt doubles the problem (retry #1 spawns another webServer)
|
|
43
|
-
- Can accumulate 8+ zombie processes across a debugging session
|
|
44
|
-
- Port conflicts make ALL subsequent tests fail
|
|
45
|
-
|
|
46
|
-
## Prevention
|
|
47
|
-
|
|
48
|
-
### 1. Document Sequential Execution Requirement
|
|
49
|
-
|
|
50
|
-
In your testing docs (e.g., `tests/SAFEWORD.md`), add prominent warning:
|
|
51
|
-
|
|
52
|
-
```markdown
|
|
53
|
-
### Zombie processes / Port already in use
|
|
54
|
-
|
|
55
|
-
**NEVER run multiple `pnpm test` commands simultaneously**
|
|
56
|
-
|
|
57
|
-
- Only run ONE test command at a time
|
|
58
|
-
- Wait for previous test run to complete before starting another
|
|
59
|
-
|
|
60
|
-
Why: Each test run spawns its own webServer. Multiple parallel runs
|
|
61
|
-
fight for same port, creating zombie processes.
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### 2. Configure playwright.config.ts for Safety
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
export default defineConfig({
|
|
68
|
-
// In CI: workers=1 (sequential)
|
|
69
|
-
// Locally: workers=undefined (allows parallelism within single run)
|
|
70
|
-
workers: process.env.CI ? 1 : undefined,
|
|
71
|
-
|
|
72
|
-
webServer: {
|
|
73
|
-
command: 'npm run dev',
|
|
74
|
-
url: 'http://localhost:3000',
|
|
75
|
-
timeout: 120000,
|
|
76
|
-
reuseExistingServer: !process.env.CI, // Reuse locally, fresh in CI
|
|
77
|
-
stdout: 'pipe', // Capture output for debugging hangs
|
|
78
|
-
stderr: 'pipe', // Capture errors
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**Key settings:**
|
|
84
|
-
|
|
85
|
-
- `reuseExistingServer: !process.env.CI` - Reuse server locally (faster), fresh in CI (reliable)
|
|
86
|
-
- `stdout: 'pipe'` / `stderr: 'pipe'` - Capture output to debug when webServer hangs
|
|
87
|
-
- `workers: 1` in CI - Forces sequential execution where parallelism is most dangerous
|
|
88
|
-
|
|
89
|
-
### 3. Provide Clear Cleanup Instructions
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
# Kill all processes on test ports
|
|
93
|
-
lsof -ti:3000 | xargs kill -9 2>/dev/null
|
|
94
|
-
lsof -ti:3001 | xargs kill -9 2>/dev/null
|
|
95
|
-
|
|
96
|
-
# Or kill by process name pattern
|
|
97
|
-
pkill -9 -f "playwright.*test-name"
|
|
98
|
-
pkill -9 -f "next dev"
|
|
99
|
-
|
|
100
|
-
# Wait before starting tests again
|
|
101
|
-
sleep 5
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## Debugging Zombie Processes
|
|
105
|
-
|
|
106
|
-
### Symptoms
|
|
107
|
-
|
|
108
|
-
- Error: `Port 3000 is in use`
|
|
109
|
-
- Error: `Timed out waiting 120000ms from config.webServer`
|
|
110
|
-
- Tests hang during retry phase
|
|
111
|
-
- `ps aux | grep test` shows multiple test processes
|
|
112
|
-
|
|
113
|
-
### Root Cause Investigation
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
# Check for background test processes
|
|
117
|
-
ps aux | grep -E "(playwright|next dev|pnpm test)"
|
|
118
|
-
|
|
119
|
-
# Check what's using the port
|
|
120
|
-
lsof -i:3000
|
|
121
|
-
|
|
122
|
-
# Check for orphaned Node processes
|
|
123
|
-
pgrep -fl node
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Fix
|
|
127
|
-
|
|
128
|
-
1. **Kill all test-related processes**
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
pkill -9 -f "pnpm test"
|
|
132
|
-
pkill -9 -f "next dev"
|
|
133
|
-
pkill -9 -f "playwright"
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
2. **Verify ports are clear**
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
lsof -i:3000 # Should return nothing
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
3. **Wait before restarting**
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
sleep 5 # Give processes time to fully terminate
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
4. **Review process management**
|
|
149
|
-
- Check if any terminals have background jobs (`jobs` command)
|
|
150
|
-
- Check if any tests are running in other terminal windows
|
|
151
|
-
- Restart terminal session if processes won't die
|
|
152
|
-
|
|
153
|
-
## Testing Trap
|
|
154
|
-
|
|
155
|
-
**Tests pass locally but fail in CI:**
|
|
156
|
-
|
|
157
|
-
- **Cause:** Local machine has `reuseExistingServer: true`, so dev server stays running across test runs
|
|
158
|
-
- **In CI:** Fresh server every time, exposes timing/startup issues
|
|
159
|
-
- **Fix:** Occasionally test locally with `reuseExistingServer: false` to catch CI-only failures
|
|
160
|
-
|
|
161
|
-
**Tests hang during retry phase:**
|
|
162
|
-
|
|
163
|
-
- **Cause:** Retry spawns another webServer while first is still running
|
|
164
|
-
- **Amplification:** Each retry doubles the zombie processes (1 → 2 → 4 → 8)
|
|
165
|
-
- **Fix:**
|
|
166
|
-
- Reduce retries: `retries: process.env.CI ? 2 : 1`
|
|
167
|
-
- Fix flaky tests so retries aren't needed
|
|
168
|
-
- Add debugging output to understand why tests are failing
|
|
169
|
-
|
|
170
|
-
## Examples
|
|
171
|
-
|
|
172
|
-
### ✅ Good: Sequential Execution in Documentation
|
|
173
|
-
|
|
174
|
-
````markdown
|
|
175
|
-
## Running Tests
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
# Run all tests (ONE command at a time)
|
|
179
|
-
pnpm test
|
|
180
|
-
|
|
181
|
-
# Run specific test file
|
|
182
|
-
pnpm test -- path/to/file.test.ts
|
|
183
|
-
|
|
184
|
-
# IMPORTANT: Wait for test run to complete before starting another
|
|
185
|
-
```
|
|
186
|
-
````
|
|
187
|
-
|
|
188
|
-
````
|
|
189
|
-
|
|
190
|
-
### ✅ Good: Clear Warning in CI/CD Setup
|
|
191
|
-
|
|
192
|
-
```yaml
|
|
193
|
-
# .github/workflows/test.yml
|
|
194
|
-
- name: Run E2E tests
|
|
195
|
-
run: pnpm test
|
|
196
|
-
# NOTE: Do not run multiple test commands in parallel
|
|
197
|
-
# Each spawns a webServer competing for ports
|
|
198
|
-
````
|
|
199
|
-
|
|
200
|
-
### ❌ Bad: No Warning About Parallel Execution
|
|
201
|
-
|
|
202
|
-
````markdown
|
|
203
|
-
## Running Tests
|
|
204
|
-
|
|
205
|
-
```bash
|
|
206
|
-
pnpm test # Run all tests
|
|
207
|
-
```
|
|
208
|
-
````
|
|
209
|
-
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
**Problem:** Developers don't know that running `pnpm test` in multiple terminals simultaneously will cause zombie processes.
|
|
213
|
-
|
|
214
|
-
## Key Principles
|
|
215
|
-
|
|
216
|
-
1. **Educate developers** - Most don't realize background processes are still running
|
|
217
|
-
2. **Enforce sequential execution** - Document it prominently in testing guides
|
|
218
|
-
3. **Add debugging visibility** - Use `stdout: 'pipe'` to capture webServer output
|
|
219
|
-
4. **Provide cleanup scripts** - Make it easy to kill zombie processes when they happen
|
|
220
|
-
5. **Test locally like CI** - Occasionally disable `reuseExistingServer` to catch CI-only issues
|
|
221
|
-
|
|
222
|
-
## Related Patterns
|
|
223
|
-
|
|
224
|
-
- **Test retry amplification** - Each retry doubles zombie processes
|
|
225
|
-
- **Port conflict cascades** - One zombie process blocks all future test runs
|
|
226
|
-
- **Silent background jobs** - Users don't notice processes still running after terminal close
|
|
227
|
-
|
|
228
|
-
## Reference
|
|
229
|
-
|
|
230
|
-
Discovered during demo mode E2E testing implementation (2025-11-01). See project-specific learning for demo mode testing patterns.
|
|
231
|
-
```
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# Milkdown Crepe .editor Property is Official Public API
|
|
2
|
-
|
|
3
|
-
**Principle:** `crepe.editor` is the **official way** to access the underlying Milkdown Editor and register custom plugins - it's not a hack.
|
|
4
|
-
|
|
5
|
-
## The Gotcha
|
|
6
|
-
|
|
7
|
-
When extending Crepe with custom ProseMirror plugins, you might think accessing `.editor` is bypassing the public API:
|
|
8
|
-
|
|
9
|
-
❌ **Assumption:** "We're accessing an internal property"
|
|
10
|
-
✅ **Reality:** `.editor` is explicitly declared in TypeScript definitions as a public getter
|
|
11
|
-
|
|
12
|
-
**Why it matters:** This is a **correct, supported pattern** for extending Crepe. No need to replace Crepe with core Milkdown just to avoid this "hack."
|
|
13
|
-
|
|
14
|
-
## Evidence
|
|
15
|
-
|
|
16
|
-
From `@milkdown/crepe` TypeScript definitions (`node_modules/@milkdown/crepe/lib/types/core/builder.d.ts:18`):
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
export declare class CrepeBuilder {
|
|
20
|
-
#private;
|
|
21
|
-
constructor({ root, defaultValue }?: CrepeBuilderConfig);
|
|
22
|
-
addFeature: { /* ... */ };
|
|
23
|
-
create: () => Promise<Editor>;
|
|
24
|
-
destroy: () => Promise<Editor>;
|
|
25
|
-
get editor(): Editor; // ← PUBLIC API GETTER
|
|
26
|
-
get readonly(): boolean;
|
|
27
|
-
setReadonly: (value: boolean) => this;
|
|
28
|
-
getMarkdown: () => string;
|
|
29
|
-
on: (fn: (api: ListenerManager) => void) => this;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export declare class Crepe extends CrepeBuilder {
|
|
33
|
-
// Inherits all properties from CrepeBuilder, including .editor getter
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Correct Usage Pattern
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
import { Crepe } from '@milkdown/crepe';
|
|
41
|
-
import { trackChangesPlugin } from './plugins/trackChangesPlugin';
|
|
42
|
-
|
|
43
|
-
const crepe = new Crepe({ root, defaultValue: '', features: {} });
|
|
44
|
-
|
|
45
|
-
// ✅ CORRECT - Official public API
|
|
46
|
-
crepe.editor.use(trackChangesPlugin);
|
|
47
|
-
|
|
48
|
-
return crepe.editor;
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## When to Use This Pattern
|
|
52
|
-
|
|
53
|
-
Use `crepe.editor.use()` when you need to:
|
|
54
|
-
|
|
55
|
-
- Register custom ProseMirror plugins that Crepe doesn't provide
|
|
56
|
-
- Access the underlying Milkdown Editor instance for direct API calls
|
|
57
|
-
- Configure plugins that aren't available through Crepe's feature system
|
|
58
|
-
|
|
59
|
-
## Alternative (Not Recommended)
|
|
60
|
-
|
|
61
|
-
You could replace Crepe with core Milkdown:
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
// ❌ NOT RECOMMENDED - Loses Crepe's pre-built features
|
|
65
|
-
import { Editor } from '@milkdown/core';
|
|
66
|
-
|
|
67
|
-
return Editor.make().use(commonmark).use(listener).use(history).use(trackChangesPlugin);
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
**Why not recommended:**
|
|
71
|
-
|
|
72
|
-
- Lose Crepe's toolbar, slash commands, block editing UI
|
|
73
|
-
- 2-3 weeks to rebuild features
|
|
74
|
-
- More code to maintain
|
|
75
|
-
- Violates "avoid bloat" principle
|
|
76
|
-
- Zero user value
|
|
77
|
-
|
|
78
|
-
## Research Sources
|
|
79
|
-
|
|
80
|
-
- **GitHub Discussion:** [How to add extra plugins to the crepe editor? #1432](https://github.com/orgs/Milkdown/discussions/1432)
|
|
81
|
-
- Maintainer confirmed: "Register the plugin as usual" after fixing rollup bug in v7.5.2
|
|
82
|
-
- **Web Search:** Multiple examples show `crepe.editor.use()` pattern in production code
|
|
83
|
-
- **TypeScript Definitions:** Explicit public getter declaration
|
|
84
|
-
|
|
85
|
-
## Conclusion
|
|
86
|
-
|
|
87
|
-
When you see `crepe.editor.use(plugin)` in code reviews, **don't flag it as a hack**. It's the correct, documented way to extend Crepe with custom functionality.
|
|
88
|
-
|
|
89
|
-
**Decision:** Keep the current implementation. No refactoring needed.
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
**Related Files:**
|
|
94
|
-
|
|
95
|
-
- `src/components/Editor.tsx` - Uses this pattern to register trackChangesPlugin
|
|
96
|
-
- `planning/design/track-changes-refactoring-plan.md` - Initially flagged as hack, now corrected
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
# ProseMirror Fragment Traversal
|
|
2
|
-
|
|
3
|
-
**Principle:** `Fragment.forEach()` only iterates direct children, not descendants. Use recursive traversal to process nested text nodes.
|
|
4
|
-
|
|
5
|
-
## The Gotcha
|
|
6
|
-
|
|
7
|
-
When applying marks to typed text in ProseMirror, `Fragment.forEach()` doesn't reach text nodes nested inside block nodes (paragraphs, headings, etc.).
|
|
8
|
-
|
|
9
|
-
❌ **Bad:** Iterating fragments without recursion
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
// This ONLY sees paragraph nodes, not text inside them
|
|
13
|
-
const markedNodes: Node[] = [];
|
|
14
|
-
fragment.forEach((node: Node) => {
|
|
15
|
-
if (node.isText) {
|
|
16
|
-
markedNodes.push(node.mark([...node.marks, insertionMark]));
|
|
17
|
-
}
|
|
18
|
-
// Paragraph node with text children → mark NOT applied
|
|
19
|
-
});
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
✅ **Good:** Recursive traversal
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
function markFragmentRecursive(fragment: Fragment, mark: Mark): Fragment {
|
|
26
|
-
const markedNodes: Node[] = [];
|
|
27
|
-
|
|
28
|
-
fragment.forEach((node: Node) => {
|
|
29
|
-
if (node.isText) {
|
|
30
|
-
// Text node - add mark
|
|
31
|
-
markedNodes.push(node.mark([...node.marks, mark]));
|
|
32
|
-
} else if (node.content.size > 0) {
|
|
33
|
-
// Block node with children - recursively mark children
|
|
34
|
-
const markedContent = markFragmentRecursive(node.content, mark);
|
|
35
|
-
markedNodes.push(node.copy(markedContent));
|
|
36
|
-
} else {
|
|
37
|
-
// Leaf node without content - keep as-is
|
|
38
|
-
markedNodes.push(node);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
return Fragment.from(markedNodes);
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Why it matters:** User types "hello" → ProseMirror wraps it in paragraph → `Fragment → Paragraph → Text("hello")`. Without recursion, `forEach()` only sees the paragraph node, and marks are applied to the wrong level.
|
|
47
|
-
|
|
48
|
-
## Document Structure
|
|
49
|
-
|
|
50
|
-
ProseMirror documents are tree structures:
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
Fragment (from ReplaceStep.slice.content)
|
|
54
|
-
├─ Paragraph node
|
|
55
|
-
│ └─ Text("hello") ← forEach() DOESN'T reach this
|
|
56
|
-
└─ Heading node
|
|
57
|
-
└─ Text("world") ← forEach() DOESN'T reach this
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Testing Trap
|
|
61
|
-
|
|
62
|
-
Tests might pass if you manually create flat fragments in test fixtures:
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
// ✅ Test passes (flat structure)
|
|
66
|
-
const testFragment = Fragment.from(schema.text('hello'));
|
|
67
|
-
// Fragment → Text("hello") ← forEach() DOES reach this
|
|
68
|
-
|
|
69
|
-
// ❌ Real app breaks (nested structure)
|
|
70
|
-
// User types "hello" → editor wraps in paragraph
|
|
71
|
-
// Fragment → Paragraph → Text("hello") ← forEach() DOESN'T reach this
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
**Solution:** Test with realistic document structures (paragraphs, headings) that match actual user input, not artificially flat fragments.
|
|
75
|
-
|
|
76
|
-
## When to Use Recursive Traversal
|
|
77
|
-
|
|
78
|
-
Use recursive traversal when:
|
|
79
|
-
|
|
80
|
-
- Processing text content across entire document (search, replace, mark application)
|
|
81
|
-
- Transforming node structure at any depth (format conversion, sanitization)
|
|
82
|
-
- Analyzing document content (word count, spell check)
|
|
83
|
-
|
|
84
|
-
Use simple `forEach()` when:
|
|
85
|
-
|
|
86
|
-
- Only processing top-level nodes (block-level operations)
|
|
87
|
-
- Explicitly working with known flat structures
|
|
88
|
-
|
|
89
|
-
## Reference Pattern
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
// Recursive helper for deep traversal
|
|
93
|
-
function processFragmentRecursive(fragment: Fragment, operation: (node: Node) => Node): Fragment {
|
|
94
|
-
const processedNodes: Node[] = [];
|
|
95
|
-
|
|
96
|
-
fragment.forEach((node: Node) => {
|
|
97
|
-
if (node.isText) {
|
|
98
|
-
processedNodes.push(operation(node));
|
|
99
|
-
} else if (node.content.size > 0) {
|
|
100
|
-
const processedContent = processFragmentRecursive(node.content, operation);
|
|
101
|
-
processedNodes.push(node.copy(processedContent));
|
|
102
|
-
} else {
|
|
103
|
-
processedNodes.push(node);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
return Fragment.from(processedNodes);
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Additional Resources
|
|
112
|
-
|
|
113
|
-
- [ProseMirror Node docs](https://prosemirror.net/docs/ref/#model.Node) - `node.copy()` for immutable updates
|
|
114
|
-
- [Fragment API](https://prosemirror.net/docs/ref/#model.Fragment) - `forEach()` behavior
|
|
115
|
-
- Track Changes implementation example: `packages/web/src/plugins/trackChangesPlugin.ts:17-45`
|
|
116
|
-
|
|
117
|
-
## Summary
|
|
118
|
-
|
|
119
|
-
**Fragment.forEach() is shallow.** Always use recursive traversal when processing text content, as ProseMirror wraps text in block nodes (paragraphs, headings). Test with realistic nested structures, not flat fragments.
|
package/packages/cli/AGENTS.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**
|