Skip to content

Commit d5ce747

Browse files
authored
ct-list takes a cell (#1439)
* ct-list takes a cell * Extract cell controller * ct-list uses cell-controller * Handle deletion of items gracefully * Format * Subtasks plus documentation cleanup * Add review-docs command * Fix lint
1 parent 6c16c0f commit d5ce747

File tree

12 files changed

+1977
-175
lines changed

12 files changed

+1977
-175
lines changed

.claude/commands/handlers-guide.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
# CommonTools Handler Patterns Guide
2+
3+
## Handler Function Structure
4+
5+
Handlers in CommonTools follow a specific three-parameter pattern:
6+
7+
```typescript
8+
const myHandler = handler(
9+
eventSchema, // What data comes from UI events
10+
stateSchema, // What data the handler needs to operate on
11+
handlerFunction // The actual function that executes
12+
);
13+
```
14+
15+
## Common Handler Patterns
16+
17+
### 1. Simple Click Handler (No Event Data)
18+
19+
```typescript
20+
const increment = handler(
21+
Record<PropertyKey, never>, // No event data needed
22+
{
23+
type: "object",
24+
properties: {
25+
count: { type: "number" }
26+
}
27+
},
28+
(_event, { count }) => {
29+
count.set(count.get() + 1);
30+
}
31+
);
32+
33+
// Usage in UI
34+
<button onClick={increment({ count: myCount })}>
35+
+1
36+
</button>
37+
```
38+
39+
### 2. Event Data Handler (Form Input, etc.)
40+
41+
```typescript
42+
const updateTitle = handler(
43+
{
44+
type: "object",
45+
properties: {
46+
detail: {
47+
type: "object",
48+
properties: { value: { type: "string" } }
49+
}
50+
}
51+
},
52+
{
53+
type: "object",
54+
properties: {
55+
title: { type: "string" }
56+
}
57+
},
58+
({ detail }, { title }) => {
59+
title.set(detail.value);
60+
}
61+
);
62+
63+
// Usage in UI
64+
<ct-input
65+
value={title}
66+
onct-input={updateTitle({ title })}
67+
/>
68+
```
69+
70+
## Common TypeScript Issues & Fixes
71+
72+
### Empty Object Types
73+
**Don't use**: `{}`
74+
**Use instead**: `Record<PropertyKey, never>`
75+
76+
### Empty Parameter Destructuring
77+
**Don't use**: `({}, state) =>`
78+
**Use instead**: `(_event, state) =>` or `(_props, state) =>`
79+
80+
### Handler Function Parameters
81+
- **First parameter**: Event data (from UI interactions)
82+
- **Second parameter**: State data (destructured from state schema)
83+
- **Parameter naming**: Use descriptive names or `_` prefix for unused parameters
84+
85+
## Handler Invocation Patterns
86+
87+
### State Passing
88+
When invoking handlers in UI, pass an object that matches your state schema:
89+
90+
```typescript
91+
// Handler definition state schema
92+
{
93+
type: "object",
94+
properties: {
95+
items: { type: "array" },
96+
currentPage: { type: "string" }
97+
}
98+
}
99+
100+
// Handler invocation - must match state schema
101+
onClick={myHandler({ items: itemsArray, currentPage: currentPageString })}
102+
```
103+
104+
### Event vs Props Confusion
105+
- **Event schema**: Describes data coming FROM the UI event
106+
- **State schema**: Describes data the handler needs to ACCESS
107+
- **Invocation object**: Must match the state schema, NOT the event schema
108+
109+
## Debugging Handler Issues
110+
111+
### Type Mismatches
112+
1. Check that handler invocation object matches state schema
113+
2. Verify event schema matches actual UI event structure
114+
3. Ensure destructuring in handler function matches schemas
115+
116+
### Runtime Issues
117+
1. Use `console.log` in handler functions to debug
118+
2. Check that state objects have expected properties
119+
3. Verify UI events are firing correctly
120+
121+
## Best Practices
122+
123+
1. **Use meaningful parameter names**: `(formData, { items, title })` not `(event, state)`
124+
2. **Keep event schemas minimal**: Often `Record<PropertyKey, never>` for simple clicks
125+
3. **Make state schemas explicit**: Always define the exact properties needed
126+
4. **Match invocation to state schema**: The object passed to handler() should match state schema exactly
127+
5. **Prefer descriptive handler names**: `updateItemTitle` not `handleUpdate`
128+
129+
## Examples by Use Case
130+
131+
### Counter/Simple State
132+
```typescript
133+
const increment = handler(
134+
Record<PropertyKey, never>,
135+
{ count: { asCell: true, type: "number" } },
136+
(_, { count }) => count.set(count.get() + 1)
137+
);
138+
```
139+
140+
### Form/Input Updates
141+
```typescript
142+
const updateField = handler(
143+
{ detail: { value: { type: "string" } } },
144+
{ fieldValue: { type: "string" } },
145+
({ detail }, { fieldValue }) => fieldValue.set(detail.value)
146+
);
147+
```
148+
149+
### List/Array Operations
150+
```typescript
151+
const addItem = handler(
152+
{ message: { type: "string" } },
153+
{ items: { type: "array", asCell: true } },
154+
({ message }, { items }) => items.push({ title: message, done: false })
155+
);
156+
```
157+
158+
### Complex State Updates
159+
```typescript
160+
const updatePageContent = handler(
161+
{ detail: { value: { type: "string" } } },
162+
{
163+
pages: { type: "object" },
164+
currentPage: { type: "string" }
165+
},
166+
({ detail }, { pages, currentPage }) => {
167+
pages[currentPage] = detail.value;
168+
}
169+
);
170+
```
171+
172+
## Advanced Patterns
173+
174+
### Handler Composition
175+
```typescript
176+
// Base handlers for reuse
177+
const createTimestamp = () => Date.now();
178+
179+
const addItemWithTimestamp = handler(
180+
{ title: { type: "string" } },
181+
{ items: { type: "array", asCell: true } },
182+
({ title }, { items }) => {
183+
items.push({
184+
title,
185+
done: false,
186+
createdAt: createTimestamp()
187+
});
188+
}
189+
);
190+
```
191+
192+
### Conditional State Updates
193+
```typescript
194+
const toggleItem = handler(
195+
Record<PropertyKey, never>,
196+
{
197+
item: { type: "object" },
198+
items: { type: "array", asCell: true }
199+
},
200+
(_, { item, items }) => {
201+
const index = items.findIndex(i => i.id === item.id);
202+
if (index !== -1) {
203+
items[index].done = !items[index].done;
204+
}
205+
}
206+
);
207+
```
208+
209+
## Common Pitfalls
210+
211+
### 1. Schema Mismatch
212+
```typescript
213+
// ❌ Wrong: State schema doesn't match invocation
214+
const handler = handler(
215+
Record<PropertyKey, never>,
216+
{ count: { type: "number" } },
217+
(_, { count }) => { ... }
218+
);
219+
220+
// Invocation passes wrong shape
221+
onClick={handler({ value: 5 })} // Should be { count: 5 }
222+
```
223+
224+
### 2. Event Schema Over-specification
225+
```typescript
226+
// ❌ Wrong: Over-complicated event schema for simple clicks
227+
const handler = handler(
228+
{
229+
type: "object",
230+
properties: {
231+
target: { type: "object" },
232+
currentTarget: { type: "object" }
233+
}
234+
},
235+
// ... rest
236+
);
237+
238+
// ✅ Better: Simple clicks rarely need event data
239+
const handler = handler(
240+
Record<PropertyKey, never>,
241+
// ... rest
242+
);
243+
```
244+
245+
### 3. Mutation vs Immutability
246+
```typescript
247+
// ❌ Wrong: Direct assignment to non-cell state
248+
(_, { title }) => {
249+
title = newValue; // This won't work
250+
}
251+
252+
// ✅ Right: Use cell methods for reactive state
253+
(_, { title }) => {
254+
title.set(newValue); // For cells
255+
}
256+
257+
// ✅ Right: Mutate arrays/objects directly for non-cell state
258+
(_, { items }) => {
259+
items.push(newItem); // For regular arrays
260+
}
261+
```
262+
263+
## Testing Handlers
264+
265+
### Unit Testing Pattern
266+
```typescript
267+
// Test handler logic separately
268+
const testState = { items: [], currentPage: "test" };
269+
const testEvent = { detail: { value: "new content" } };
270+
271+
// Call handler function directly
272+
handlerFunction(testEvent, testState);
273+
274+
// Assert expected changes
275+
expect(testState.items).toHaveLength(1);
276+
```
277+
278+
### Integration Testing
279+
```typescript
280+
// Test full handler including schemas
281+
const handler = createHandler(...);
282+
const testInvocation = { items: mockItems, currentPage: "test" };
283+
284+
// Test that handler can be invoked without errors
285+
expect(() => handler(testInvocation)).not.toThrow();
286+
```

.claude/commands/logs.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
the logs of past sessions in this repository are stored in `~/.claude/projects/` under the `pwd` of this project. determine the correct folder, then list the jsonl files within.
2+
3+
## Log Analysis Strategy
4+
5+
1. **Use ripgrep (rg) for fast searching** across all log files:
6+
```bash
7+
rg "pattern" ~/.claude/projects/-Users-ben-code-labs/*.jsonl
8+
```
9+
10+
2. **Log file structure**: Each line is a JSON object with:
11+
- `message.content`: Main content (string or array of objects)
12+
- `type`: "user" or "assistant"
13+
- `timestamp`: ISO timestamp
14+
- `uuid`: Unique message ID
15+
- `parentUuid`: Links to previous message
16+
17+
3. **Effective search patterns**:
18+
- For keywords: `rg -i "keyword1|keyword2" logs/*.jsonl`
19+
- For conversation flow: Use `jq` to follow parentUuid chains
20+
- For time ranges: `rg "2025-07-21" logs/*.jsonl`
21+
- For tool usage: `rg "tool_use.*ToolName" logs/*.jsonl`
22+
23+
4. **Find distraction patterns**:
24+
- Look for TodoWrite usage showing task switches
25+
- Search for "interrupt", "Request interrupted", or scope changes
26+
- Find where assistant mentions getting confused or changing direction
27+
- Check for long Task tool usage that might indicate research tangents
28+
29+
5. **Analyze conversation flow**:
30+
- Sample log format first with `head -2 file.jsonl | jq .`
31+
- Use `jq` to extract message content: `jq '.message.content' file.jsonl`
32+
- Look for thinking content: `jq 'select(.message.content[0].type == "thinking")' file.jsonl`
33+
34+
6. **Common analysis queries**:
35+
```bash
36+
# Find all TodoWrite usage
37+
rg "TodoWrite" logs/*.jsonl | head -10
38+
39+
# Find task interruptions
40+
rg "interrupt|distract|confused|forgot" logs/*.jsonl
41+
42+
# Find specific feature work
43+
rg "ct-list|context.menu" logs/*.jsonl
44+
45+
# Extract conversation summary
46+
jq -r '.message.content | if type == "string" then . else .[0].text // "" end' file.jsonl | head -20
47+
```
48+
49+
The user has asked you to search for: $ARGUMENTS

0 commit comments

Comments
 (0)