Add .claude to gitignore, remove from repo
Made-with: Cursor
This commit is contained in:
parent
c477110f21
commit
c491ec89a1
12 changed files with 1 additions and 1771 deletions
|
|
@ -1,198 +0,0 @@
|
|||
---
|
||||
name: claude-md-auditor
|
||||
description: "Use this agent when CLAUDE.md files or .claude/rules/ files may have drifted from the actual codebase state. This includes after significant refactors, feature additions, file renames, directory restructuring, or when you suspect documentation no longer matches reality. Use proactively after any large code change.\\n\\nExamples:\\n\\n- Example 1:\\n user: \"Refactor the services directory to split analysis into separate modules\"\\n assistant: \"I've completed the refactoring of the services directory. Let me now launch the claude-md-auditor agent to ensure all CLAUDE.md files reflect these structural changes.\"\\n <commentary>\\n Since a significant refactor was performed that likely changed file paths, exports, and directory structure documented in CLAUDE.md files, use the Task tool to launch the claude-md-auditor agent.\\n </commentary>\\n\\n- Example 2:\\n user: \"Add a new notifications feature with hooks, components, and a store slice\"\\n assistant: \"The notifications feature is now implemented. I'll use the claude-md-auditor agent to update the documentation to include the new files and patterns.\"\\n <commentary>\\n A new feature was added with multiple new files across components, hooks, and store slices. The CLAUDE.md files likely need updating to document these additions. Use the Task tool to launch the claude-md-auditor agent.\\n </commentary>\\n\\n- Example 3:\\n user: \"Rename isRealUserMessage to isParsedRealUserMessage across the codebase\"\\n assistant: \"The rename is complete across all source and test files. Now I'll launch the claude-md-auditor agent to update any documentation references to the old function name.\"\\n <commentary>\\n A function was renamed which is likely documented in CLAUDE.md type guard tables and conventions sections. Use the Task tool to launch the claude-md-auditor agent to fix stale references.\\n </commentary>\\n\\n- Example 4:\\n user: \"Can you audit the CLAUDE.md files to make sure they're up to date?\"\\n assistant: \"I'll launch the claude-md-auditor agent to systematically verify all documentation against the actual codebase.\"\\n <commentary>\\n The user explicitly requested a documentation audit. Use the Task tool to launch the claude-md-auditor agent.\\n </commentary>"
|
||||
model: opus
|
||||
color: green
|
||||
memory: project
|
||||
---
|
||||
|
||||
You are an elite CLAUDE.md auditor and documentation integrity specialist. Your sole purpose is to ensure every `CLAUDE.md` file and `.claude/rules/*.md` file in the project accurately reflects the current codebase state. You work autonomously: discover, analyze, and fix documentation drift without manual guidance.
|
||||
|
||||
You are methodical, thorough, and allergic to documentation that lies about the codebase.
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Truth from codebase, not docs** — The filesystem is the source of truth. If a CLAUDE.md says a file exists but `Glob` can't find it, the doc is wrong.
|
||||
|
||||
2. **Max 200 lines per file** — Keep files concise. Split if over limit.
|
||||
|
||||
3. **Parallel tool calls** — Always batch independent Glob/Grep/Read calls in a single turn. Never sequentially read files that can be read in parallel. This is critical for performance.
|
||||
|
||||
4. **Surgical edits** — Use Edit (not Write) for existing files. Change only what's wrong. Don't rewrite entire files when a few lines need fixing.
|
||||
|
||||
5. **No invention** — Only document what actually exists. Never add aspirational content.
|
||||
|
||||
6. **Preserve voice and style** — Match the existing writing style of each file. Don't introduce new formatting patterns unless the file has none.
|
||||
|
||||
7. **Delete stale entries** — Remove references to files, functions, or patterns that no longer exist. Don't comment them out.
|
||||
|
||||
8. **Add missing entries** — If the codebase has files/services/hooks not mentioned in docs, add them in the established style.
|
||||
|
||||
## Process
|
||||
|
||||
### Phase 1: Discovery (parallel)
|
||||
|
||||
**Check your agent memory first.** Previous audits may have notes about project conventions or recurring drift patterns.
|
||||
|
||||
Make ALL of these calls in a single turn:
|
||||
|
||||
- `Glob: **/CLAUDE.md`
|
||||
- `Glob: .claude/rules/*.md`
|
||||
- `Glob: src/**/*.ts` (to understand actual structure)
|
||||
- `Glob: src/**/*.tsx`
|
||||
- `Glob: test/**/*.test.ts`
|
||||
|
||||
Then, in the next turn, read every discovered CLAUDE.md and rules file in a single parallel batch.
|
||||
|
||||
### Phase 2: Cross-Reference Analysis
|
||||
|
||||
For each CLAUDE.md file, verify every claim against the actual codebase:
|
||||
|
||||
| Documented Item | Verification Method |
|
||||
|----------------|-------------------|
|
||||
| File/directory exists | `Glob` for the path |
|
||||
| Export name is correct | `Grep` for the export |
|
||||
| Function/hook name | `Grep` for the definition |
|
||||
| Service/class name | `Grep` for `class X` or `export.*X` |
|
||||
| Method count (e.g., "9 methods") | Count actual methods |
|
||||
| Test file listing | `Glob` for test directory |
|
||||
| CSS variable names | `Grep` in index.css |
|
||||
| Command names (pnpm scripts) | Read package.json `scripts` |
|
||||
|
||||
**Batch verification calls**: Group all Grep/Glob checks for a single CLAUDE.md file into one parallel turn. Then move to the next file.
|
||||
|
||||
### Common Drift Patterns to Catch
|
||||
|
||||
- **Renamed exports**: Function/type names changed but docs still reference old names
|
||||
- **Missing new files**: New services/hooks/utils added but not documented
|
||||
- **Deleted files**: Old entries referencing removed code
|
||||
- **Wrong counts**: "11 slices" when there are now 12
|
||||
- **Wrong descriptions**: File purpose changed but doc wasn't updated
|
||||
- **Missing subdirectories**: New `utils/` or `hooks/` folders not listed
|
||||
- **Stale commands**: Build/test commands that changed in package.json
|
||||
- **Moved files**: Files relocated to different directories
|
||||
- **Changed import paths**: Path aliases or barrel exports changed
|
||||
|
||||
### Phase 3: Parallel Updates
|
||||
|
||||
Group all edits by file. For each CLAUDE.md that needs changes:
|
||||
|
||||
1. **Use Edit tool** with precise `old_string` → `new_string` replacements
|
||||
2. **Make multiple Edit calls per turn** for independent files
|
||||
3. **Only use Write** if creating a new CLAUDE.md file that doesn't exist yet
|
||||
|
||||
Decision matrix:
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Entry references non-existent file | Delete the entry |
|
||||
| New file exists but undocumented | Add entry in alphabetical order |
|
||||
| Name/path changed | Update to current name/path |
|
||||
| Count is wrong | Update the number |
|
||||
| Sections accurate | Leave untouched |
|
||||
| Entire file is obsolete | Delete the file |
|
||||
| Directory needs docs but has none | Create new CLAUDE.md |
|
||||
|
||||
### Phase 4: Verification
|
||||
|
||||
After all edits, do a final pass:
|
||||
1. Re-read each modified file to confirm edits applied correctly
|
||||
2. Check line counts (warn if any file exceeds 200 lines)
|
||||
3. Cross-check: spot-verify 3-5 entries from each file against codebase
|
||||
|
||||
## Output Format
|
||||
|
||||
When finished, return a concise summary:
|
||||
|
||||
```
|
||||
## CLAUDE.md Audit Complete
|
||||
|
||||
### Files Modified
|
||||
- `path/CLAUDE.md` — [what changed: added X, removed Y, fixed Z]
|
||||
- ...
|
||||
|
||||
### Files Created
|
||||
- `path/CLAUDE.md` — [why it was needed]
|
||||
|
||||
### Files Deleted
|
||||
- `path/CLAUDE.md` — [why it was obsolete]
|
||||
|
||||
### No Changes Needed
|
||||
- `path/CLAUDE.md` — accurate as-is
|
||||
|
||||
### Stats
|
||||
- Files audited: N
|
||||
- Files modified: N
|
||||
- Entries added: N
|
||||
- Entries removed: N
|
||||
- Entries corrected: N
|
||||
```
|
||||
|
||||
## Critical Rules
|
||||
|
||||
**ALWAYS verify before editing.** Never assume a documented entry is wrong without checking the actual codebase first.
|
||||
|
||||
**PARALLEL, PARALLEL, PARALLEL.** Every turn should have multiple tool calls unless there's a data dependency. Reading 10 files? One turn, 10 Read calls. Checking 15 exports? One turn, 15 Grep calls.
|
||||
|
||||
**Don't touch non-documentation files.** You modify ONLY `**/CLAUDE.md` and `.claude/rules/*.md` files. Never edit source code, tests, or config files.
|
||||
|
||||
**Respect .claude/rules/ glob patterns.** Rules files may have YAML frontmatter with `globs:` that control when they're loaded. Don't change the globs unless the file patterns genuinely changed.
|
||||
|
||||
**No commits.** Return results only. The caller decides whether to commit.
|
||||
|
||||
**Update your agent memory** as you discover project conventions, recurring drift patterns, file organization quirks, naming conventions, and areas of the codebase that frequently change. This builds up institutional knowledge across audits. Write concise notes about what you found and where.
|
||||
|
||||
Examples of what to record:
|
||||
- Directories or files that are frequently renamed or restructured
|
||||
- Naming conventions for exports, hooks, utilities, and services
|
||||
- Common patterns of documentation drift (e.g., counts going stale, renamed type guards)
|
||||
- Which CLAUDE.md files cover which parts of the codebase
|
||||
- Project-specific conventions that affect how documentation should be written
|
||||
- Files or sections that were accurate and rarely drift (low-priority for future audits)
|
||||
|
||||
# Persistent Agent Memory
|
||||
|
||||
You have a persistent Persistent Agent Memory directory at `.claude/agent-memory/claude-md-auditor/`. Its contents persist across conversations.
|
||||
|
||||
As you work, consult your memory files to build on previous experience. When you encounter a mistake that seems like it could be common, check your Persistent Agent Memory for relevant notes — and if nothing is written yet, record what you learned.
|
||||
|
||||
Guidelines:
|
||||
- `MEMORY.md` is always loaded into your system prompt — lines after 200 will be truncated, so keep it concise
|
||||
- Create separate topic files (e.g., `debugging.md`, `patterns.md`) for detailed notes and link to them from MEMORY.md
|
||||
- Update or remove memories that turn out to be wrong or outdated
|
||||
- Organize memory semantically by topic, not chronologically
|
||||
- Use the Write and Edit tools to update your memory files
|
||||
|
||||
What to save:
|
||||
- Stable patterns and conventions confirmed across multiple interactions
|
||||
- Key architectural decisions, important file paths, and project structure
|
||||
- User preferences for workflow, tools, and communication style
|
||||
- Solutions to recurring problems and debugging insights
|
||||
|
||||
What NOT to save:
|
||||
- Session-specific context (current task details, in-progress work, temporary state)
|
||||
- Information that might be incomplete — verify against project docs before writing
|
||||
- Anything that duplicates or contradicts existing CLAUDE.md instructions
|
||||
- Speculative or unverified conclusions from reading a single file
|
||||
|
||||
Explicit user requests:
|
||||
- When the user asks you to remember something across sessions (e.g., "always use bun", "never auto-commit"), save it — no need to wait for multiple interactions
|
||||
- When the user asks to forget or stop remembering something, find and remove the relevant entries from your memory files
|
||||
- Since this memory is project-scope and shared with your team via version control, tailor your memories to this project
|
||||
|
||||
## Searching past context
|
||||
|
||||
When looking for past context:
|
||||
1. Search topic files in your memory directory:
|
||||
```
|
||||
Grep with pattern="<search term>" path="<repo-root>/.claude/agent-memory/claude-md-auditor/" glob="*.md"
|
||||
```
|
||||
2. Session transcript logs (last resort — large files, slow):
|
||||
```
|
||||
Grep with pattern="<search term>" path="~/.claude/projects/<encoded-path>/" glob="*.jsonl"
|
||||
```
|
||||
Use narrow search terms (error messages, file paths, function names) rather than broad keywords.
|
||||
|
||||
## MEMORY.md
|
||||
|
||||
Your MEMORY.md is currently empty. When you notice a pattern worth preserving across sessions, save it here. Anything in MEMORY.md will be included in your system prompt next time.
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
---
|
||||
name: quality-fixer
|
||||
description: "Use this agent when the user wants to fix all code quality issues in the project, including linting, formatting, and unused code detection. This agent runs `pnpm fix` followed by `pnpm quality` in a loop, delegating each iteration to a subagent, until all issues are resolved.\\n\\nExamples:\\n\\n- User: \"Fix all the quality issues\"\\n Assistant: \"I'll launch the quality-fixer agent to iteratively fix all linting, formatting, and quality issues.\"\\n (Uses Task tool to launch quality-fixer agent)\\n\\n- User: \"Run quality checks and fix everything\"\\n Assistant: \"Let me use the quality-fixer agent to handle that.\"\\n (Uses Task tool to launch quality-fixer agent)\\n\\n- User: \"Make sure the code passes all checks\"\\n Assistant: \"I'll use the quality-fixer agent to ensure all quality checks pass.\"\\n (Uses Task tool to launch quality-fixer agent)\\n\\n- After completing a large refactor or feature implementation:\\n Assistant: \"Now that the changes are complete, let me launch the quality-fixer agent to ensure everything passes quality checks.\"\\n (Uses Task tool to launch quality-fixer agent)"
|
||||
model: opus
|
||||
color: red
|
||||
---
|
||||
|
||||
You are an elite code quality engineer specializing in automated code quality remediation. Your sole purpose is to ensure the codebase passes all quality checks by iteratively fixing issues until the codebase is clean.
|
||||
|
||||
## Project Context
|
||||
- This is an Electron + React + TypeScript project using pnpm
|
||||
- Quality commands:
|
||||
- `pnpm fix` = runs `pnpm lint:fix && pnpm format` (auto-fixes lint and formatting)
|
||||
- `pnpm quality` = runs `pnpm check && pnpm format:check && npx knip` (type checking, format verification, unused code detection)
|
||||
- Path aliases: `@main/*`, `@renderer/*`, `@shared/*`, `@preload/*`
|
||||
|
||||
## Core Process
|
||||
|
||||
You operate in a **loop** where each iteration is delegated to a **subagent** via the Task tool. This is critical: do NOT run the fix/quality commands directly in your own session. Every iteration MUST be dispatched as a subagent.
|
||||
|
||||
### Loop Structure
|
||||
|
||||
**Iteration N (each via a Task tool subagent):**
|
||||
|
||||
1. **Subagent prompt must instruct the subagent to:**
|
||||
a. Run `pnpm fix` and capture the full output
|
||||
b. Run `pnpm quality` and capture the full output
|
||||
c. If `pnpm quality` succeeds (exit code 0, no errors), report SUCCESS
|
||||
d. If `pnpm quality` fails, analyze the error output carefully and fix all reported issues:
|
||||
- **TypeScript errors** (`pnpm check`): Fix type errors, missing imports, incorrect types
|
||||
- **Format issues** (`pnpm format:check`): These should be auto-fixed by `pnpm fix`, but if persistent, manually fix formatting
|
||||
- **Knip issues** (`npx knip`): Remove unused exports, unused dependencies, unused files, unused types
|
||||
e. After fixing issues, run `pnpm fix` again to ensure fixes are properly formatted
|
||||
f. Report back: what was fixed, what errors remain (if any), and whether quality passed
|
||||
|
||||
2. **After receiving the subagent's report:**
|
||||
- If the subagent reports SUCCESS (all quality checks pass), you are DONE. Report the final status.
|
||||
- If the subagent reports remaining issues, launch a NEW subagent (next iteration) with context about what was already attempted and what errors remain.
|
||||
|
||||
### Subagent Prompt Template
|
||||
|
||||
When launching each subagent via the Task tool, provide a detailed prompt like this:
|
||||
|
||||
```
|
||||
You are fixing code quality issues in this project. This is iteration {N} of the quality fix loop.
|
||||
|
||||
{If iteration > 1: "Previous iteration found these remaining issues: {paste remaining errors}"}
|
||||
|
||||
Steps:
|
||||
1. Run `pnpm fix` to auto-fix lint and formatting issues. Show the output.
|
||||
2. Run `pnpm quality` to check for remaining issues. Show the full output.
|
||||
3. If quality passes with no errors, report "SUCCESS: All quality checks pass."
|
||||
4. If quality fails, carefully analyze EVERY error and fix them:
|
||||
- For TypeScript errors: fix the type issues in the relevant files
|
||||
- For knip (unused code) errors: remove unused exports, imports, dependencies, or files
|
||||
- For format errors: fix formatting manually if pnpm fix didn't catch it
|
||||
5. After making fixes, run `pnpm fix` one more time to ensure your changes are properly formatted.
|
||||
6. Report what you fixed and any remaining errors you could not resolve.
|
||||
|
||||
IMPORTANT:
|
||||
- Fix ALL issues, not just some of them
|
||||
- When removing unused exports, check if they're used elsewhere before removing
|
||||
- For knip unused dependency warnings, remove them from package.json
|
||||
- For knip unused file warnings, verify the file is truly unused before deleting
|
||||
- Use path aliases (@main/*, @renderer/*, @shared/*, @preload/*) for any new imports
|
||||
```
|
||||
|
||||
### Safety Rules
|
||||
|
||||
1. **Maximum 5 iterations**. If after 5 loops quality still doesn't pass, stop and report the remaining issues to the user with a clear summary of what was fixed and what remains.
|
||||
2. **Never delete files without verification** — when knip reports unused files, the subagent should verify they're truly unused.
|
||||
3. **Never remove exports that are used** — when knip reports unused exports, verify they're not imported elsewhere.
|
||||
4. **Preserve functionality** — fixes should only address quality issues, never change application behavior.
|
||||
5. **Each subagent gets full context** — always pass remaining errors from the previous iteration to the next subagent so it doesn't repeat failed approaches.
|
||||
|
||||
### Reporting
|
||||
|
||||
After the loop completes (either success or max iterations), provide a summary:
|
||||
- Total iterations run
|
||||
- Issues found and fixed (categorized by type: lint, format, types, unused code)
|
||||
- Final status: PASS or FAIL with remaining issues
|
||||
- Files modified
|
||||
|
||||
**Update your agent memory** as you discover common quality issues, recurring lint violations, frequently flagged unused exports, and knip patterns in this codebase. This builds up institutional knowledge across conversations. Write concise notes about what you found and where.
|
||||
|
||||
Examples of what to record:
|
||||
- Common TypeScript errors that recur (e.g., specific type mismatches)
|
||||
- Files or exports frequently flagged by knip
|
||||
- Lint rules that frequently need fixing
|
||||
- Patterns that tend to cause quality check failures
|
||||
|
|
@ -1,389 +0,0 @@
|
|||
---
|
||||
name: claude-devtools:chatgroup-architecture
|
||||
description: ChatGroup architecture — how conversation data flows from raw JSONL to rendered chat groups. Use when working on UserGroup, AIGroup, SystemGroup, display items, tool linking, chunks, or the rendering hierarchy.
|
||||
---
|
||||
|
||||
# ChatGroup Architecture
|
||||
|
||||
How conversation data flows from raw JSONL messages to rendered chat groups.
|
||||
|
||||
## Core Design Principle
|
||||
|
||||
Chat groups are **independent items in a flat chronological list**, not paired turns.
|
||||
There is no UserTurn/AITurn pairing — each group stands alone.
|
||||
|
||||
```typescript
|
||||
// src/renderer/types/groups.ts
|
||||
export type ChatItem =
|
||||
| { type: 'user'; group: UserGroup }
|
||||
| { type: 'system'; group: SystemGroup }
|
||||
| { type: 'ai'; group: AIGroup }
|
||||
| { type: 'compact'; group: CompactGroup };
|
||||
|
||||
export interface SessionConversation {
|
||||
sessionId: string;
|
||||
items: ChatItem[]; // Flat chronological list
|
||||
totalUserGroups: number;
|
||||
totalSystemGroups: number;
|
||||
totalAIGroups: number;
|
||||
totalCompactGroups: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Pipeline Overview
|
||||
|
||||
```
|
||||
Raw JSONL messages
|
||||
→ MessageClassifier (classify into user/system/ai/hardNoise)
|
||||
→ ChunkBuilder (buffer AI messages, flush on user/system boundary)
|
||||
→ ChunkFactory (build EnhancedAIChunk with SemanticSteps)
|
||||
→ groupTransformer (chunks → flat ChatItem[] conversation)
|
||||
→ aiGroupEnhancer (AIGroup → EnhancedAIGroup with displayItems, linkedTools, lastOutput)
|
||||
→ React components render
|
||||
```
|
||||
|
||||
Primary source files:
|
||||
|
||||
- `src/main/services/parsing/MessageClassifier.ts`
|
||||
- `src/main/services/analysis/ChunkBuilder.ts`
|
||||
- `src/main/services/analysis/ChunkFactory.ts`
|
||||
- `src/renderer/utils/groupTransformer.ts`
|
||||
- `src/renderer/utils/aiGroupEnhancer.ts`
|
||||
- `src/renderer/utils/displayItemBuilder.ts`
|
||||
- `src/renderer/types/groups.ts`
|
||||
- `src/main/types/chunks.ts`
|
||||
|
||||
## Data Models
|
||||
|
||||
### UserGroup
|
||||
|
||||
```typescript
|
||||
// src/renderer/types/groups.ts
|
||||
interface UserGroup {
|
||||
id: string;
|
||||
message: ParsedMessage;
|
||||
timestamp: Date;
|
||||
content: UserGroupContent;
|
||||
index: number; // Ordering index within session
|
||||
}
|
||||
|
||||
interface UserGroupContent {
|
||||
text?: string; // Plain text (commands removed)
|
||||
rawText?: string; // Original text
|
||||
commands: CommandInfo[]; // Extracted /commands
|
||||
images: ImageData[]; // Attached images
|
||||
fileReferences: FileReference[]; // @file.ts mentions
|
||||
}
|
||||
```
|
||||
|
||||
Renders right-aligned blue bubble. Contains markdown text, slash commands, images, and file references.
|
||||
|
||||
### AIGroup
|
||||
|
||||
```typescript
|
||||
interface AIGroup {
|
||||
id: string;
|
||||
turnIndex: number; // 0-based (for turn navigation)
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
durationMs: number;
|
||||
steps: SemanticStep[]; // Core semantic steps
|
||||
tokens: AIGroupTokens;
|
||||
summary: AIGroupSummary; // For collapsed view
|
||||
status: AIGroupStatus; // 'complete' | 'interrupted' | 'error' | 'in_progress'
|
||||
processes: Process[]; // Subagent processes
|
||||
chunkId: string;
|
||||
metrics: SessionMetrics;
|
||||
responses: ParsedMessage[]; // All assistant + internal messages
|
||||
isOngoing?: boolean; // True for last group in ongoing session
|
||||
}
|
||||
```
|
||||
|
||||
### EnhancedAIGroup
|
||||
|
||||
The renderer enhances `AIGroup` before rendering:
|
||||
|
||||
```typescript
|
||||
interface EnhancedAIGroup extends AIGroup {
|
||||
lastOutput: AIGroupLastOutput | null; // Always-visible output
|
||||
displayItems: AIGroupDisplayItem[]; // Flattened chronological items
|
||||
linkedTools: Map<string, LinkedToolItem>; // Tool call/result pairs
|
||||
itemsSummary: string; // "2 thinking, 4 tool calls, 3 subagents"
|
||||
mainModel: ModelInfo | null;
|
||||
subagentModels: ModelInfo[];
|
||||
claudeMdStats: ClaudeMdStats | null;
|
||||
}
|
||||
```
|
||||
|
||||
Enhancement happens in `src/renderer/utils/aiGroupEnhancer.ts`:
|
||||
|
||||
1. `findLastOutput` — extracts the final visible output (text, tool result, interruption, plan exit, ongoing)
|
||||
2. `linkToolCallsToResults` — pairs tool calls with their results into `LinkedToolItem`
|
||||
3. `buildDisplayItems` — flattens steps into chronological `AIGroupDisplayItem[]`
|
||||
4. `buildSummary` — generates human-readable summary string
|
||||
5. `extractMainModel` / `extractSubagentModels` — extracts model info
|
||||
|
||||
### AIGroupDisplayItem
|
||||
|
||||
```typescript
|
||||
type AIGroupDisplayItem =
|
||||
| { type: 'thinking'; content: string; timestamp: Date; tokenCount?: number }
|
||||
| { type: 'tool'; tool: LinkedToolItem }
|
||||
| { type: 'subagent'; subagent: Process }
|
||||
| { type: 'output'; content: string; timestamp: Date; tokenCount?: number }
|
||||
| { type: 'slash'; slash: SlashItem }
|
||||
| { type: 'teammate_message'; teammateMessage: TeammateMessage };
|
||||
```
|
||||
|
||||
Display items are sorted chronologically in `src/renderer/utils/displayItemBuilder.ts`.
|
||||
|
||||
### LinkedToolItem
|
||||
|
||||
```typescript
|
||||
interface LinkedToolItem {
|
||||
id: string;
|
||||
name: string;
|
||||
input: Record<string, unknown>;
|
||||
callTokens?: number;
|
||||
result?: {
|
||||
content: string | unknown[];
|
||||
isError: boolean;
|
||||
toolUseResult?: ToolUseResultData;
|
||||
tokenCount?: number;
|
||||
};
|
||||
inputPreview: string; // First 100 chars
|
||||
outputPreview?: string; // First 200 chars
|
||||
startTime: Date;
|
||||
endTime?: Date;
|
||||
durationMs?: number;
|
||||
isOrphaned: boolean; // No result received
|
||||
sourceModel?: string;
|
||||
skillInstructions?: string; // For Skill tool calls
|
||||
skillInstructionsTokenCount?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### AIGroupLastOutput
|
||||
|
||||
Always-visible output below the AI group header:
|
||||
|
||||
```typescript
|
||||
interface AIGroupLastOutput {
|
||||
type: 'text' | 'tool_result' | 'interruption' | 'ongoing' | 'plan_exit';
|
||||
text?: string;
|
||||
toolName?: string;
|
||||
toolResult?: string;
|
||||
isError?: boolean;
|
||||
interruptionMessage?: string;
|
||||
planContent?: string;
|
||||
planPreamble?: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
```
|
||||
|
||||
### SystemGroup
|
||||
|
||||
```typescript
|
||||
interface SystemGroup {
|
||||
id: string;
|
||||
message: ParsedMessage;
|
||||
timestamp: Date;
|
||||
commandOutput: string;
|
||||
commandName?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Renders left-aligned with neutral gray styling. Monospace pre with ANSI escape codes cleaned.
|
||||
|
||||
### CompactGroup
|
||||
|
||||
```typescript
|
||||
interface CompactGroup {
|
||||
id: string;
|
||||
timestamp: Date;
|
||||
message: ParsedMessage;
|
||||
tokenDelta?: CompactionTokenDelta;
|
||||
startingPhaseNumber?: number;
|
||||
}
|
||||
```
|
||||
|
||||
Visual boundary for context compaction events.
|
||||
|
||||
## Backend: Chunk Building
|
||||
|
||||
### Message Classification
|
||||
|
||||
`src/main/services/parsing/MessageClassifier.ts` classifies raw messages into 4 categories:
|
||||
|
||||
| Category | Description | Result |
|
||||
|----------|-------------|--------|
|
||||
| `user` | Genuine user input | Creates UserChunk, renders right |
|
||||
| `system` | Command output | Creates SystemChunk, renders left |
|
||||
| `ai` | Assistant responses | Buffered into AIChunk, renders left |
|
||||
| `hardNoise` | System metadata, caveats, reminders | Filtered out entirely |
|
||||
|
||||
### Chunk Building
|
||||
|
||||
`src/main/services/analysis/ChunkBuilder.ts` buffers AI messages and flushes on boundaries:
|
||||
|
||||
```
|
||||
for each classified message:
|
||||
hardNoise → skip
|
||||
compact → flush AI buffer, emit CompactChunk
|
||||
user → flush AI buffer, emit UserChunk
|
||||
system → flush AI buffer, emit SystemChunk
|
||||
ai → add to AI buffer
|
||||
```
|
||||
|
||||
AI buffer is flushed when a non-AI message arrives, producing one `AIChunk` per contiguous run of assistant messages.
|
||||
|
||||
### Semantic Step Extraction
|
||||
|
||||
`src/main/services/analysis/ChunkFactory.ts` enriches AI chunks:
|
||||
|
||||
1. Build tool executions from message content blocks
|
||||
2. Collect sidechain messages within the time range
|
||||
3. Link subagent processes to the chunk
|
||||
4. Extract semantic steps (`SemanticStep[]`)
|
||||
5. Fill timeline gaps
|
||||
6. Calculate step context accumulation
|
||||
7. Build step groups
|
||||
|
||||
```typescript
|
||||
type SemanticStepType = 'thinking' | 'tool_call' | 'tool_result' | 'subagent' | 'output' | 'interruption';
|
||||
|
||||
interface SemanticStep {
|
||||
id: string;
|
||||
type: SemanticStepType;
|
||||
startTime: Date;
|
||||
endTime?: Date;
|
||||
durationMs: number;
|
||||
content: { /* type-specific fields */ };
|
||||
tokens?: { input: number; output: number; cached?: number };
|
||||
context: 'main' | 'subagent';
|
||||
}
|
||||
```
|
||||
|
||||
## Rendering
|
||||
|
||||
### Component Hierarchy
|
||||
|
||||
```
|
||||
ChatHistory
|
||||
└─ ChatHistoryItem (router)
|
||||
├─ UserChatGroup → right-aligned blue bubble
|
||||
├─ SystemChatGroup → left-aligned gray block
|
||||
├─ AIChatGroup → left-aligned, collapsible
|
||||
│ ├─ Header (model, summary, tokens, duration, timestamp)
|
||||
│ ├─ DisplayItemList (when expanded)
|
||||
│ │ ├─ ThinkingItem
|
||||
│ │ ├─ LinkedToolItem (Read, Edit, Write, Skill, etc.)
|
||||
│ │ ├─ SubagentItem (with nested trace)
|
||||
│ │ ├─ TextItem
|
||||
│ │ ├─ SlashItem
|
||||
│ │ └─ TeammateMessageItem
|
||||
│ └─ LastOutputDisplay (always visible)
|
||||
└─ CompactBoundary
|
||||
```
|
||||
|
||||
Primary render files:
|
||||
|
||||
- `src/renderer/components/chat/ChatHistory.tsx`
|
||||
- `src/renderer/components/chat/ChatHistoryItem.tsx`
|
||||
- `src/renderer/components/chat/UserChatGroup.tsx`
|
||||
- `src/renderer/components/chat/SystemChatGroup.tsx`
|
||||
- `src/renderer/components/chat/AIChatGroup.tsx`
|
||||
- `src/renderer/components/chat/DisplayItemList.tsx`
|
||||
- `src/renderer/components/chat/LastOutputDisplay.tsx`
|
||||
|
||||
### AI Group: Collapsed vs Expanded
|
||||
|
||||
**Collapsed** (default):
|
||||
- Header: Bot icon, "Claude", model badge, items summary, chevron
|
||||
- Right side: context badge, token usage, duration, timestamp
|
||||
- Last output (always visible below header)
|
||||
|
||||
**Expanded**:
|
||||
- All collapsed content, plus:
|
||||
- `DisplayItemList` with chronologically ordered items
|
||||
- Each display item can be individually expanded (nested expansion)
|
||||
|
||||
### Last Output Rendering
|
||||
|
||||
Always visible regardless of expansion state. Renders based on `type`:
|
||||
|
||||
| Type | Rendering |
|
||||
|------|-----------|
|
||||
| `text` | Markdown in code-bg rounded block |
|
||||
| `tool_result` | Tool name + pre-formatted result |
|
||||
| `interruption` | Warning banner with AlertTriangle |
|
||||
| `plan_exit` | Plan preamble + plan content in special block |
|
||||
| `ongoing` | Ongoing session banner (last AI group only) |
|
||||
|
||||
## Per-Tab UI State Isolation
|
||||
|
||||
Each tab maintains **completely independent** expansion state via `tabUISlice.ts`:
|
||||
|
||||
```typescript
|
||||
interface TabUIState {
|
||||
expandedAIGroupIds: Set<string>;
|
||||
expandedDisplayItemIds: Map<string, Set<string>>;
|
||||
expandedSubagentTraceIds: Set<string>;
|
||||
showContextPanel: boolean;
|
||||
selectedContextPhase: number | null;
|
||||
savedScrollTop?: number;
|
||||
}
|
||||
```
|
||||
|
||||
Accessed via `useTabUI()` hook which reads `tabId` from `TabUIContext`:
|
||||
|
||||
```typescript
|
||||
// src/renderer/hooks/useTabUI.tsx
|
||||
const { isAIGroupExpanded, toggleAIGroupExpansion, expandAIGroup,
|
||||
getExpandedDisplayItemIds, toggleDisplayItemExpansion,
|
||||
isSubagentTraceExpanded, toggleSubagentTraceExpansion } = useTabUI();
|
||||
```
|
||||
|
||||
Auto-expansion triggers:
|
||||
- Error deep linking (contains highlighted error tool)
|
||||
- Search results (contains search match)
|
||||
|
||||
## Store Integration
|
||||
|
||||
### Session Detail Slice
|
||||
|
||||
`src/renderer/store/slices/sessionDetailSlice.ts` manages the fetch pipeline:
|
||||
|
||||
```
|
||||
fetchSessionDetail(projectId, sessionId, tabId?)
|
||||
→ IPC: getSessionDetail (returns chunks + processes)
|
||||
→ transformChunksToConversation (chunks → ChatItem[])
|
||||
→ processSessionClaudeMd (compute CLAUDE.md stats)
|
||||
→ processSessionContextWithPhases (compute context stats)
|
||||
→ store in global state + per-tab tabSessionData
|
||||
```
|
||||
|
||||
Key state:
|
||||
|
||||
| Field | Purpose |
|
||||
|-------|---------|
|
||||
| `sessionDetail` | Raw session data from main process |
|
||||
| `conversation` | Transformed `SessionConversation` |
|
||||
| `conversationLoading` | True during fetch (causes ChatHistory unmount) |
|
||||
| `tabSessionData` | Per-tab copies of session data |
|
||||
| `sessionClaudeMdStats` | CLAUDE.md injection stats per AI group |
|
||||
| `sessionContextStats` | Context stats per AI group |
|
||||
| `sessionPhaseInfo` | Phase boundary info |
|
||||
|
||||
### Real-Time Updates
|
||||
|
||||
`refreshSessionInPlace` re-fetches and transforms without setting `conversationLoading: true`, avoiding ChatHistory unmount/remount flicker.
|
||||
|
||||
## Invariants
|
||||
|
||||
1. Chat items are always flat and chronological — no nesting at the conversation level.
|
||||
2. AI groups are self-contained — all semantic steps, tool links, and display items are computed per group.
|
||||
3. Display items within an AI group are chronologically sorted.
|
||||
4. Per-tab UI state is fully isolated — expanding a group in one tab doesn't affect another.
|
||||
5. Last output is always visible regardless of AI group expansion state.
|
||||
6. `conversationLoading: true` unmounts ChatHistory — avoid setting it unnecessarily for existing tabs.
|
||||
|
|
@ -1,356 +0,0 @@
|
|||
---
|
||||
name: claude-devtools:design-system
|
||||
description: Design system and visual language — theming, CSS variables, Tailwind config, component styling patterns, icon usage, animations, and z-index layers. Use when creating or modifying UI components, working with the dark/light theme, or debugging visual issues.
|
||||
---
|
||||
|
||||
# Design System & Visual Language
|
||||
|
||||
How the theming, color palette, component patterns, and styling conventions work.
|
||||
|
||||
## Theme Architecture
|
||||
|
||||
Two themes (dark/light) driven by CSS custom properties in `src/renderer/index.css`.
|
||||
Toggled via `useTheme()` hook which adds/removes `light` class on `document.documentElement`.
|
||||
|
||||
Flash prevention: a script in `index.html` applies the cached theme before React loads.
|
||||
|
||||
### Theme Hook
|
||||
|
||||
```typescript
|
||||
// src/renderer/hooks/useTheme.ts
|
||||
const { theme, resolvedTheme, isDark, isLight } = useTheme();
|
||||
// theme: 'dark' | 'light' | 'system'
|
||||
// resolvedTheme: 'dark' | 'light' (after system resolution)
|
||||
```
|
||||
|
||||
## Styling Convention
|
||||
|
||||
**Colors**: Always via CSS variables (theme-aware). Use inline `style` or Tailwind classes mapped to variables.
|
||||
**Layout/spacing**: Tailwind utility classes.
|
||||
**Icons**: `lucide-react` with `size-*` Tailwind classes.
|
||||
|
||||
```tsx
|
||||
// Preferred: inline style for theme-aware colors, Tailwind for layout
|
||||
<div className="flex items-center gap-2 rounded-md px-3 py-2"
|
||||
style={{ backgroundColor: 'var(--color-surface-raised)', color: 'var(--color-text)' }}>
|
||||
<Bot className="size-4 shrink-0" style={{ color: COLOR_TEXT_SECONDARY }} />
|
||||
</div>
|
||||
|
||||
// Also valid: Tailwind classes that reference CSS variables
|
||||
<div className="bg-surface text-text border-border">
|
||||
<div className="bg-surface-raised text-text-secondary">
|
||||
```
|
||||
|
||||
### TypeScript Constants
|
||||
|
||||
`src/renderer/constants/cssVariables.ts` centralizes CSS variable strings:
|
||||
|
||||
```typescript
|
||||
import { COLOR_TEXT_MUTED, CARD_BG, CARD_BORDER_STYLE } from '@renderer/constants/cssVariables';
|
||||
|
||||
<span style={{ color: COLOR_TEXT_MUTED }}>Muted text</span>
|
||||
<div style={{ backgroundColor: CARD_BG, border: CARD_BORDER_STYLE }}>Card</div>
|
||||
```
|
||||
|
||||
Constants cover: text colors, surfaces, borders, code blocks, diff, cards, tags, prose.
|
||||
|
||||
## CSS Variable Reference
|
||||
|
||||
All defined in `src/renderer/index.css` under `:root` (dark) and `:root.light`.
|
||||
|
||||
### Surfaces
|
||||
|
||||
| Variable | Dark | Light | Usage |
|
||||
|----------|------|-------|-------|
|
||||
| `--color-surface` | `#141416` | `#f9f9f7` | Main background |
|
||||
| `--color-surface-raised` | `#27272a` | `#f0efed` | Elevated surfaces |
|
||||
| `--color-surface-overlay` | `#27272a` | `#e8e7e4` | Overlays/modals |
|
||||
| `--color-surface-sidebar` | `#0f0f11` | `#f1f0ee` | Sidebar background |
|
||||
|
||||
### Text
|
||||
|
||||
| Variable | Dark | Light | Usage |
|
||||
|----------|------|-------|-------|
|
||||
| `--color-text` | `#fafafa` | `#1c1b19` | Primary text |
|
||||
| `--color-text-secondary` | `#a1a1aa` | `#4d4b46` | Secondary text |
|
||||
| `--color-text-muted` | `#71717a` | `#6d6b65` | Muted text |
|
||||
|
||||
### Borders
|
||||
|
||||
| Variable | Dark | Light |
|
||||
|----------|------|-------|
|
||||
| `--color-border` | `rgba(255,255,255,0.05)` | `#d5d3cf` |
|
||||
| `--color-border-subtle` | `rgba(255,255,255,0.05)` | `#e3e1dd` |
|
||||
| `--color-border-emphasis` | `rgba(255,255,255,0.1)` | `#a8a5a0` |
|
||||
|
||||
### Chat Bubbles
|
||||
|
||||
**User bubble** (right-aligned):
|
||||
| Variable | Dark | Light |
|
||||
|----------|------|-------|
|
||||
| `--chat-user-bg` | `#27272a` | `#eae9e6` |
|
||||
| `--chat-user-text` | `#a1a1aa` | `#5a5955` |
|
||||
| `--chat-user-border` | `rgba(255,255,255,0.08)` | `#d5d3cf` |
|
||||
| `--chat-user-shadow` | `0 1px 0 0 rgba(255,255,255,0.03)` | `0 1px 2px 0 rgba(0,0,0,0.04)` |
|
||||
| `--chat-user-tag-bg` | `rgba(255,255,255,0.08)` | `rgba(0,0,0,0.05)` |
|
||||
| `--chat-user-tag-text` | `#e4e4e7` | `#3a3935` |
|
||||
| `--chat-user-tag-border` | `rgba(255,255,255,0.12)` | `rgba(0,0,0,0.08)` |
|
||||
|
||||
**AI message**:
|
||||
| Variable | Dark | Light |
|
||||
|----------|------|-------|
|
||||
| `--chat-ai-border` | `rgba(255,255,255,0.05)` | `#d5d3cf` |
|
||||
| `--chat-ai-icon` | `#71717a` | `#6d6b65` |
|
||||
|
||||
**System bubble**:
|
||||
| Variable | Dark | Light |
|
||||
|----------|------|-------|
|
||||
| `--chat-system-bg` | `rgba(39,39,42,0.5)` | `#eae9e6` |
|
||||
| `--chat-system-text` | `#d4d4d8` | `#3a3935` |
|
||||
|
||||
### Code & Syntax
|
||||
|
||||
| Variable | Dark | Light |
|
||||
|----------|------|-------|
|
||||
| `--code-bg` | `#1c1c1e` | `#f0efed` |
|
||||
| `--code-header-bg` | `#1c1c1e` | `#eae9e6` |
|
||||
| `--code-border` | `rgba(255,255,255,0.1)` | `#d5d3cf` |
|
||||
| `--code-line-number` | `#52525b` | `#a8a5a0` |
|
||||
| `--code-filename` | `#60a5fa` | `#2563eb` |
|
||||
| `--inline-code-bg` | `rgba(255,255,255,0.08)` | `rgba(0,0,0,0.05)` |
|
||||
| `--inline-code-text` | `#e4e4e7` | `#3a3935` |
|
||||
|
||||
Syntax highlighting: `--syntax-string`, `--syntax-comment`, `--syntax-number`, `--syntax-keyword`, `--syntax-type`, `--syntax-operator`, `--syntax-function`. Dark uses vibrant colors; light uses GitHub-inspired palette.
|
||||
|
||||
### Semantic Blocks
|
||||
|
||||
**Thinking**: Purple tones (`--thinking-bg`, `--thinking-border`, `--thinking-text`)
|
||||
**Tool call**: Amber tones (`--tool-call-bg`, `--tool-call-border`, `--tool-call-text`)
|
||||
**Tool result success**: Green tones (`--tool-result-success-bg/border/text`)
|
||||
**Tool result error**: Red tones (`--tool-result-error-bg/border/text`)
|
||||
**Output**: Gray tones (`--output-bg`, `--output-border`, `--output-text`)
|
||||
**Interruption**: Red (`--interruption-bg/border/text`)
|
||||
**Warning**: Amber (`--warning-bg/border/text`)
|
||||
**Plan exit**: Green (`--plan-exit-bg/header-bg/border/text`)
|
||||
|
||||
### Diff Viewer
|
||||
|
||||
| Variable | Dark | Light |
|
||||
|----------|------|-------|
|
||||
| `--diff-added-bg` | `rgba(34,197,94,0.15)` | `rgba(34,197,94,0.1)` |
|
||||
| `--diff-added-text` | `#4ade80` | `#166534` |
|
||||
| `--diff-removed-bg` | `rgba(239,68,68,0.15)` | `rgba(239,68,68,0.1)` |
|
||||
| `--diff-removed-text` | `#f87171` | `#991b1b` |
|
||||
|
||||
### Cards (Subagents)
|
||||
|
||||
| Variable | Dark | Light |
|
||||
|----------|------|-------|
|
||||
| `--card-bg` | `#121212` | `#f9f9f7` |
|
||||
| `--card-border` | `#27272a` | `#d5d3cf` |
|
||||
| `--card-header-bg` | `#18181b` | `#f0efed` |
|
||||
| `--card-header-hover` | `#1f1f23` | `#eae9e6` |
|
||||
| `--card-icon-muted` | `#52525b` | `#a8a5a0` |
|
||||
| `--card-separator` | `#3f3f46` | `#d5d3cf` |
|
||||
|
||||
### Badges
|
||||
|
||||
Status badges: `--badge-error-bg/text`, `--badge-warning-bg/text`, `--badge-success-bg/text`, `--badge-info-bg/text`, `--badge-neutral-bg/text`.
|
||||
Tags: `--tag-bg`, `--tag-text`, `--tag-border`.
|
||||
|
||||
### Search Highlights
|
||||
|
||||
| Variable | Dark | Light |
|
||||
|----------|------|-------|
|
||||
| `--highlight-bg` | `rgba(202,138,4,0.7)` | `#facc15` |
|
||||
| `--highlight-bg-inactive` | `rgba(113,63,18,0.5)` | `#fef08a` |
|
||||
| `--highlight-ring` | `#facc15` | `#ca8a04` |
|
||||
|
||||
### Scrollbar
|
||||
|
||||
Custom scrollbar styling via `--scrollbar-thumb`, `--scrollbar-thumb-hover`, `--scrollbar-thumb-active`.
|
||||
|
||||
## Tailwind Config
|
||||
|
||||
`tailwind.config.js` maps CSS variables to Tailwind classes:
|
||||
|
||||
```javascript
|
||||
colors: {
|
||||
surface: {
|
||||
DEFAULT: 'var(--color-surface)',
|
||||
raised: 'var(--color-surface-raised)',
|
||||
overlay: 'var(--color-surface-overlay)',
|
||||
sidebar: 'var(--color-surface-sidebar)',
|
||||
code: 'var(--code-bg)',
|
||||
},
|
||||
border: {
|
||||
DEFAULT: 'var(--color-border)',
|
||||
subtle: 'var(--color-border-subtle)',
|
||||
emphasis: 'var(--color-border-emphasis)',
|
||||
},
|
||||
text: {
|
||||
DEFAULT: 'var(--color-text)',
|
||||
secondary: 'var(--color-text-secondary)',
|
||||
muted: 'var(--color-text-muted)',
|
||||
},
|
||||
semantic: {
|
||||
success: '#22c55e',
|
||||
error: '#ef4444',
|
||||
warning: '#f59e0b',
|
||||
info: '#3b82f6',
|
||||
},
|
||||
// Backward-compatible alias
|
||||
'claude-dark': { bg: 'var(--color-surface)', surface: 'var(--color-surface-raised)', ... },
|
||||
}
|
||||
```
|
||||
|
||||
Plugin: `@tailwindcss/typography` for markdown prose.
|
||||
|
||||
## Icon Library
|
||||
|
||||
`lucide-react` across 55+ components.
|
||||
|
||||
**Sizes**: `size-3` (tiny), `size-3.5` (small), `size-4` (standard), `size-5` (medium), `size-10` (empty states)
|
||||
|
||||
**Pattern**: `className` for size, `style` for color:
|
||||
```tsx
|
||||
<Bot className="size-4 shrink-0" style={{ color: COLOR_TEXT_SECONDARY }} />
|
||||
<Loader2 className="size-3.5 shrink-0 animate-spin" style={{ color: '#3b82f6' }} />
|
||||
```
|
||||
|
||||
**Common icons**: `Bot` (AI), `User` (user), `ChevronRight`/`ChevronDown` (expansion), `Check`/`Copy` (clipboard), `Info` (tooltips), `Loader2` (loading), `Clock` (duration), `Terminal` (traces), `CheckCircle2` (completed).
|
||||
|
||||
## Team Colors
|
||||
|
||||
`src/renderer/constants/teamColors.ts` defines 8 color sets for teammate visualization:
|
||||
|
||||
```typescript
|
||||
import { getTeamColorSet, TeamColorSet } from '@renderer/constants/teamColors';
|
||||
|
||||
const colors = getTeamColorSet('blue');
|
||||
// colors.border: '#3b82f6' — border accent
|
||||
// colors.badge: 'rgba(59, 130, 246, 0.15)' — badge background
|
||||
// colors.text: '#60a5fa' — text color
|
||||
```
|
||||
|
||||
Available colors: blue, green, red, yellow, purple, cyan, orange, pink.
|
||||
|
||||
## Component Patterns
|
||||
|
||||
### User Chat Bubble
|
||||
|
||||
Right-aligned with rounded corners, subtle shadow, copy overlay on hover:
|
||||
|
||||
```tsx
|
||||
<div className="flex justify-end">
|
||||
<div className="max-w-[85%]">
|
||||
<div className="rounded-2xl rounded-br-sm px-4 py-3"
|
||||
style={{
|
||||
backgroundColor: 'var(--chat-user-bg)',
|
||||
border: '1px solid var(--chat-user-border)',
|
||||
boxShadow: 'var(--chat-user-shadow)',
|
||||
}}>
|
||||
<ReactMarkdown>{text}</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### AI Group Header
|
||||
|
||||
Collapsible with model badge, summary, metrics:
|
||||
|
||||
```tsx
|
||||
<div onClick={toggle} className="flex cursor-pointer items-center gap-2">
|
||||
<Bot className="size-4" style={{ color: COLOR_TEXT_SECONDARY }} />
|
||||
<span>Claude</span>
|
||||
<span className={getModelColorClass(model.family)}>{model.name}</span>
|
||||
<span style={{ color: COLOR_TEXT_MUTED }}>{itemsSummary}</span>
|
||||
<ChevronDown className={`size-3.5 transition-transform ${isExpanded ? 'rotate-180' : ''}`} />
|
||||
</div>
|
||||
```
|
||||
|
||||
### Subagent Card
|
||||
|
||||
Linear-style card with nested expansion:
|
||||
|
||||
```tsx
|
||||
<div style={{ backgroundColor: CARD_BG, border: CARD_BORDER_STYLE }}>
|
||||
<div className="flex cursor-pointer items-center gap-2 px-3 py-2"
|
||||
style={{ backgroundColor: isExpanded ? CARD_HEADER_BG : 'transparent' }}>
|
||||
<ChevronRight className={`size-3.5 transition-transform ${isExpanded ? 'rotate-90' : ''}`} />
|
||||
{/* colored dot + badge + description + metrics */}
|
||||
</div>
|
||||
{isExpanded && <div className="space-y-3 p-3">{/* content */}</div>}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Copy Button (Overlay)
|
||||
|
||||
Gradient-fade overlay that appears on group hover:
|
||||
|
||||
```tsx
|
||||
<div className="absolute right-0 top-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<div style={{ background: `linear-gradient(to right, transparent, ${bgColor})` }} />
|
||||
<button><Copy className="size-3.5" /></button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Popover via Portal
|
||||
|
||||
Token usage and context badges use portaled popovers to escape stacking context:
|
||||
|
||||
```tsx
|
||||
{showPopover && createPortal(
|
||||
<div style={{
|
||||
backgroundColor: 'var(--color-surface-raised)',
|
||||
border: '1px solid var(--color-border)',
|
||||
boxShadow: '0 10px 25px -5px rgba(0, 0, 0, 0.3)',
|
||||
zIndex: 99999,
|
||||
}}>
|
||||
{/* content */}
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
```
|
||||
|
||||
## Animations
|
||||
|
||||
### CSS Keyframes (in `index.css`)
|
||||
|
||||
- `shimmer` — skeleton loading shimmer effect
|
||||
- `skeleton-fade-in` — staggered fade-in for skeleton cards
|
||||
- `splash-slide` — splash screen loading bar
|
||||
|
||||
### Tailwind Utilities
|
||||
|
||||
| Class | Usage |
|
||||
|-------|-------|
|
||||
| `animate-spin` | Loading spinners (`Loader2`) |
|
||||
| `animate-ping` | Pulsing dots (ongoing state) |
|
||||
| `transition-transform` | Chevron rotation |
|
||||
| `transition-colors` | Hover color changes |
|
||||
| `transition-opacity` | Copy button fade-in |
|
||||
| `transition-all duration-300` | Card highlight transitions |
|
||||
| `duration-[3000ms]` | Highlight ring fade-out |
|
||||
|
||||
## Z-Index Layers
|
||||
|
||||
| Z-Index | Usage |
|
||||
|---------|-------|
|
||||
| `z-10` | Copy button overlays, dropdown backdrops |
|
||||
| `z-20` | Dropdown menus, search bar |
|
||||
| `z-30` | Pane split drop zones |
|
||||
| `z-40` | Pane view overlays |
|
||||
| `z-50` | Context menus, command palette, settings selects |
|
||||
| `99999` | Portaled popovers (token usage, context badge, metrics pill) |
|
||||
|
||||
## Light Theme Notes
|
||||
|
||||
The light theme uses **warm neutrals** (not pure white/gray):
|
||||
- Backgrounds: `#f9f9f7`, `#f0efed`, `#eae9e6` (warm off-white)
|
||||
- Borders: `#d5d3cf`, `#e3e1dd` (warm gray)
|
||||
- Text: `#1c1b19`, `#4d4b46`, `#6d6b65` (warm dark)
|
||||
- Syntax highlighting: GitHub-inspired palette
|
||||
|
||||
Body transition: `background-color 0.2s ease, color 0.2s ease` for smooth theme switching.
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
---
|
||||
name: claude-devtools:explain-visible-context
|
||||
description: Explains what "Visible Context" is — the 6 trackable token categories, what falls outside tracking, how it's displayed, and why it matters. Use when someone asks about visible context, token attribution, or context window usage.
|
||||
---
|
||||
|
||||
Present the following explanation directly to the user. Output the full content below as your response — do not summarize, ask follow-up questions, or treat this as background context.
|
||||
|
||||
# Visible Context
|
||||
|
||||
## What It Is
|
||||
|
||||
"Visible Context" is the portion of Claude's context window that we can identify and measure. Every time Claude processes a turn, its context window fills with various pieces of information — your messages, file contents, tool outputs, thinking, and more. Visible Context tracks what we **can** attribute to a known source, so you can see where your tokens are going.
|
||||
|
||||
## What We Track (6 Categories)
|
||||
|
||||
### CLAUDE.md Files
|
||||
|
||||
Memory files that Claude loads automatically at the start of every session and after each compaction. These include:
|
||||
|
||||
- **Global** CLAUDE.md (`~/.claude/CLAUDE.md`) — your personal instructions across all projects
|
||||
- **Project** CLAUDE.md (`.claude/CLAUDE.md` or `CLAUDE.md` at project root) — project-specific instructions
|
||||
- **Directory** CLAUDE.md — instructions scoped to subdirectories (e.g., `src/renderer/CLAUDE.md`)
|
||||
|
||||
These are injected repeatedly (once per compaction phase), so their token cost accumulates. A 500-token CLAUDE.md file injected across 3 compaction phases costs ~1,500 tokens total.
|
||||
|
||||
### @-Mentioned Files
|
||||
|
||||
Files you reference with `@path/to/file` in your messages. When you mention a file, Claude Code injects the full file contents into the context. Large files consume significant tokens — a 1,000-line source file could use 5,000+ tokens per mention.
|
||||
|
||||
### Tool Outputs
|
||||
|
||||
Results returned from tool executions: file reads (`Read`), command output (`Bash`), search results (`Grep`, `Glob`), and others. Every tool result stays in the context window until compaction. A `Bash` command that prints 500 lines of output or a `Read` of a large file both count here.
|
||||
|
||||
### Thinking + Text Output
|
||||
|
||||
Claude's own output that consumes context:
|
||||
|
||||
- **Extended thinking** — Claude's internal reasoning (when thinking mode is active). This can be substantial for complex tasks.
|
||||
- **Text output** — Claude's visible responses to you. Longer explanations and code blocks use more tokens.
|
||||
|
||||
### Task Coordination
|
||||
|
||||
Messages and operations from Claude Code's team/orchestration features:
|
||||
|
||||
- `SendMessage` — messages between teammates
|
||||
- `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet` — task management
|
||||
- `TeamCreate`, `TeamDelete` — team lifecycle
|
||||
|
||||
Each coordination message adds to the context window of the receiving agent.
|
||||
|
||||
### User Messages
|
||||
|
||||
Your actual prompt text for each turn. This includes the raw text you type, but not the system-injected metadata around it.
|
||||
|
||||
## What We Don't Track
|
||||
|
||||
Visible Context does **not** cover everything in Claude's context window. The following are present but not attributable by our tracking:
|
||||
|
||||
- **Claude Code's system prompt** — the base instructions that tell Claude how to behave, use tools, format output, etc.
|
||||
- **Tool descriptions** — the schema and documentation for each built-in tool (Read, Write, Edit, Bash, Grep, Glob, etc.)
|
||||
- **MCP tool descriptions** — schemas for any MCP (Model Context Protocol) servers you have connected
|
||||
- **Custom agent definitions** — instructions from `.claude/agents/` configurations
|
||||
- **Skill descriptions** — the short descriptions of available skills that Claude sees so it knows what's available (visible via `/context` in Claude Code)
|
||||
- **Internal system reminders** — `<system-reminder>` injections that Claude Code adds for session state, git status, available skills, etc.
|
||||
- **Conversation structure overhead** — the message formatting, role markers, and protocol framing around each message
|
||||
|
||||
These untracked items form a "base cost" that's always present. You can see what Claude Code injects via the `/context` command in Claude Code itself.
|
||||
|
||||
## How It's Displayed
|
||||
|
||||
### Per-Turn Popover (Context Badge)
|
||||
|
||||
Each AI group in the chat shows a small badge. Hovering reveals what was injected at that specific turn — which CLAUDE.md files, which @-mentioned files, which tool outputs contributed tokens.
|
||||
|
||||
### Token Usage Popover
|
||||
|
||||
The token count next to each AI group has an info icon. Hovering shows the standard input/output/cache breakdown, plus an expandable "Visible Context" section showing the percentage of total tokens attributable to each tracked category.
|
||||
|
||||
### Session Context Panel
|
||||
|
||||
A dedicated panel (toggle via the context badge or header button) that shows the full session-wide view:
|
||||
|
||||
- All tracked injections grouped by category
|
||||
- Token estimates per injection
|
||||
- Phase filtering (if compaction events split the session into phases)
|
||||
- Total visible context as a percentage of total session tokens
|
||||
|
||||
## Compaction Phases
|
||||
|
||||
When Claude's context window fills up, Claude Code compacts the conversation — summarizing older messages to free space. Each compaction creates a new "phase." Visible Context tracks injections per phase because:
|
||||
|
||||
- CLAUDE.md files are re-injected after each compaction
|
||||
- Previous tool outputs and file contents are summarized away
|
||||
- The phase selector lets you see what's in context **right now** (current phase) vs. what was present earlier
|
||||
|
||||
## Why Visible Context Matters
|
||||
|
||||
Understanding where tokens go helps you:
|
||||
|
||||
- **Spot expensive injections** — a massive CLAUDE.md file or a frequently-mentioned large file could be using 20%+ of your context
|
||||
- **Optimize CLAUDE.md** — keep memory files concise; every token is repeated across phases
|
||||
- **Be strategic with @-mentions** — mentioning a 2,000-line file costs real context space
|
||||
- **Understand compaction impact** — see how much context resets after compaction
|
||||
- **Debug unexpected behavior** — if Claude seems to "forget" something, check whether it was compacted away
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
---
|
||||
name: claude-devtools:markdown-search
|
||||
description: Markdown search logic — how in-session and cross-session search works. Use when working on SearchBar, search highlighting, searchHighlightUtils, markdownTextSearch, or SessionSearcher.
|
||||
---
|
||||
|
||||
# Markdown Search Logic
|
||||
|
||||
How in-session and cross-session markdown search works end-to-end.
|
||||
|
||||
## Scope
|
||||
|
||||
Current in-session search intentionally covers:
|
||||
|
||||
- User message markdown text
|
||||
- AI `lastOutput` text markdown
|
||||
|
||||
Current in-session search intentionally excludes:
|
||||
|
||||
- System items
|
||||
- Tool result text blocks
|
||||
- Thinking/subagent/internal display items
|
||||
|
||||
Primary source files:
|
||||
|
||||
- `src/renderer/components/search/SearchBar.tsx`
|
||||
- `src/renderer/store/slices/conversationSlice.ts`
|
||||
- `src/renderer/components/chat/ChatHistory.tsx`
|
||||
- `src/renderer/components/chat/searchHighlightUtils.ts`
|
||||
- `src/shared/utils/markdownTextSearch.ts`
|
||||
- `src/main/services/discovery/SessionSearcher.ts`
|
||||
|
||||
## Core Data Model
|
||||
|
||||
`SearchMatch` (renderer store) in `src/renderer/store/types.ts`:
|
||||
|
||||
- `itemId`: chat group id (`user-*`, `ai-*`)
|
||||
- `itemType`: `user | ai`
|
||||
- `matchIndexInItem`: 0-based index inside one searchable item
|
||||
- `globalIndex`: 0-based index across all matches
|
||||
- `displayItemId`: optional (`lastOutput` for AI output)
|
||||
|
||||
Important distinction:
|
||||
|
||||
- `matchIndexInItem` is local to one item.
|
||||
- `currentSearchIndex` is global position in the search result list.
|
||||
|
||||
## Pipeline Overview
|
||||
|
||||
### 1) Query input and initial match generation
|
||||
|
||||
`SearchBar` updates the query with tab-scoped conversation data:
|
||||
|
||||
- `setSearchQuery(query, conversation)` in `src/renderer/components/search/SearchBar.tsx`
|
||||
|
||||
`setSearchQuery` in `src/renderer/store/slices/conversationSlice.ts`:
|
||||
|
||||
- Scans conversation items
|
||||
- Uses `findMarkdownSearchMatches` (shared parser logic) per searchable item
|
||||
- Builds initial `searchMatches`, `searchResultCount`, `currentSearchIndex`
|
||||
|
||||
### 2) Rendering highlights
|
||||
|
||||
Search highlighting is rendered in markdown component trees through:
|
||||
|
||||
- `createSearchContext(...)` in `src/renderer/components/chat/searchHighlightUtils.ts`
|
||||
- `highlightSearchInChildren(...)` in `src/renderer/components/chat/searchHighlightUtils.ts`
|
||||
|
||||
Each rendered highlight mark includes:
|
||||
|
||||
- `data-search-item-id`
|
||||
- `data-search-match-index`
|
||||
- `data-search-result` (`current` or `match`)
|
||||
|
||||
### 3) Canonicalization to rendered DOM (critical)
|
||||
|
||||
`ChatHistory` collects rendered `<mark>` elements in DOM order and calls:
|
||||
|
||||
- `syncSearchMatchesWithRendered(renderedMatches)` in `src/renderer/store/slices/conversationSlice.ts`
|
||||
|
||||
Why this exists:
|
||||
|
||||
- Real UI navigation must match visible marks exactly.
|
||||
- Parser results can temporarily differ during render timing.
|
||||
- DOM order is the final source of truth for nth navigation.
|
||||
|
||||
Safety guard:
|
||||
|
||||
- `ChatHistory` delays syncing when a transient empty mark snapshot appears, to avoid wiping results mid-render.
|
||||
|
||||
### 4) Next/prev navigation and scrolling
|
||||
|
||||
`nextSearchResult` / `previousSearchResult` in `src/renderer/store/slices/conversationSlice.ts`:
|
||||
|
||||
- Move `currentSearchIndex` with wrap-around
|
||||
|
||||
`ChatHistory` scroll effect:
|
||||
|
||||
- First tries exact selector:
|
||||
- `mark[data-search-item-id="..."][data-search-match-index="..."]`
|
||||
- If missing, falls back to the global nth rendered mark (same `currentSearchIndex`)
|
||||
- Final fallback walks text nodes under `[data-search-content]` roots
|
||||
|
||||
## Shared Markdown Search Engine
|
||||
|
||||
`src/shared/utils/markdownTextSearch.ts` is used by both renderer and main process:
|
||||
|
||||
- `findMarkdownSearchMatches`
|
||||
- `countMarkdownSearchMatches`
|
||||
- `extractMarkdownPlainText`
|
||||
|
||||
Design principle:
|
||||
|
||||
- Search parser mirrors markdown render behavior (remark + gfm + HAST traversal)
|
||||
- Matching is segment-based (no cross-node match)
|
||||
|
||||
## Cross-Session Search (Command Palette / IPC)
|
||||
|
||||
Main process search path:
|
||||
|
||||
- IPC handler: `src/main/ipc/search.ts`
|
||||
- Engine: `src/main/services/discovery/SessionSearcher.ts`
|
||||
|
||||
`SessionSearcher` also uses shared markdown search utils, and returns:
|
||||
|
||||
- `groupId`
|
||||
- `itemType`
|
||||
- `matchIndexInItem`
|
||||
- `matchStartOffset`
|
||||
|
||||
These are passed into tab navigation context so opening a search result can jump to the exact in-session match.
|
||||
|
||||
## Invariants to Keep
|
||||
|
||||
When changing markdown/search code, keep these invariants:
|
||||
|
||||
1. Parser and renderer must agree on searchable text boundaries.
|
||||
2. `matchIndexInItem` semantics must stay stable per item.
|
||||
3. `currentSearchIndex` must represent the global nth visible match.
|
||||
4. `searchResultCount` must reflect actual rendered match count after canonicalization.
|
||||
5. Search source scope must be explicit (no accidental inclusion of hidden/internal text).
|
||||
|
||||
## If You Add New Searchable Markdown Surfaces
|
||||
|
||||
If you make a new markdown surface searchable:
|
||||
|
||||
1. Ensure it uses search context + `highlightSearchInChildren`.
|
||||
2. Ensure emitted marks include `data-search-item-id` and `data-search-match-index`.
|
||||
3. Ensure the content is included in `setSearchQuery` source scanning.
|
||||
4. Ensure parser collection logic in `src/shared/utils/markdownTextSearch.ts` still mirrors render behavior.
|
||||
5. Add/adjust alignment tests.
|
||||
|
||||
## Debug Playbook
|
||||
|
||||
Enable debug logs:
|
||||
|
||||
- `localStorage.setItem('search-debug', '1')`
|
||||
|
||||
Useful logs:
|
||||
|
||||
- `[search] query` / `[search] sample` from `setSearchQuery`
|
||||
- `[search] sync-rendered` from DOM canonicalization
|
||||
- `[search] next` / `[search] prev` navigation logs
|
||||
|
||||
Quick checks when behavior is off:
|
||||
|
||||
1. Compare `searchResultCount` vs number of rendered marks.
|
||||
2. Verify `currentSearchIndex` increments exactly once per click.
|
||||
3. Check whether exact mark selector exists for current match.
|
||||
4. Confirm the active tab conversation is the same one used for `setSearchQuery`.
|
||||
5. Confirm virtualization is disabled during active search.
|
||||
|
||||
## Tests
|
||||
|
||||
Main tests relevant to this logic:
|
||||
|
||||
- `test/shared/utils/markdownTextSearch.test.ts`
|
||||
- `test/shared/utils/markdownSearchRendererAlignment.test.ts`
|
||||
|
||||
The alignment test ensures parser match indexes and rendered mark indexes stay identical across representative markdown cases.
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
---
|
||||
name: claude-devtools:navigation-scroll
|
||||
description: Navigation and scroll orchestration — tab navigation, error highlights, search scrolling, auto-scroll coordination, and common bug patterns. Use when working on useTabNavigationController, scroll restore, or navigation requests.
|
||||
---
|
||||
|
||||
# Navigation & Scroll Orchestration
|
||||
|
||||
How tab navigation (error highlights, search scrolling, auto-scroll) works end-to-end.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Navigation Request Model (Nonce-Based)
|
||||
|
||||
```typescript
|
||||
// src/renderer/types/tabs.ts
|
||||
interface TabNavigationRequest {
|
||||
id: string; // crypto.randomUUID() — fresh nonce per click
|
||||
kind: 'error' | 'search' | 'autoBottom';
|
||||
highlight: 'red' | 'yellow' | 'none';
|
||||
payload: ErrorNavigationPayload | SearchNavigationPayload | {};
|
||||
source: 'notification' | 'triggerPreview' | 'commandPalette' | 'sessionOpen';
|
||||
}
|
||||
|
||||
// Stored on Tab:
|
||||
interface Tab {
|
||||
pendingNavigation?: TabNavigationRequest; // Set by enqueue, cleared by consume
|
||||
lastConsumedNavigationId?: string; // Tracks last processed request
|
||||
}
|
||||
```
|
||||
|
||||
### Store Actions (tabSlice.ts)
|
||||
|
||||
| Action | Purpose |
|
||||
|--------|---------|
|
||||
| `enqueueTabNavigation(tabId, request)` | Set `pendingNavigation` on a tab |
|
||||
| `consumeTabNavigation(tabId, requestId)` | Clear `pendingNavigation`, record `lastConsumedNavigationId` |
|
||||
|
||||
### Navigation Sources
|
||||
|
||||
| Source | Slice | Creates |
|
||||
|--------|-------|---------|
|
||||
| Notification click / test trigger | `notificationSlice.navigateToError()` | `ErrorNavigationRequest` (red) |
|
||||
| CommandPalette search result | `tabSlice.navigateToSession()` | `SearchNavigationRequest` (yellow) |
|
||||
|
||||
### Controller Hook: `useTabNavigationController`
|
||||
|
||||
**Location:** `src/renderer/hooks/useTabNavigationController.ts`
|
||||
|
||||
Phase state machine:
|
||||
```
|
||||
idle → pending → expanding → scrolling → highlighting → complete → idle
|
||||
```
|
||||
|
||||
Key behaviors:
|
||||
- **Active-tab-only:** Ignores `!isActiveTab` to prevent cross-tab races
|
||||
- **Nonce dedup:** `activeRequestIdRef.current === pendingNavigation.id` prevents reprocessing
|
||||
- **Failure debounce:** 500ms cooldown after failed navigation (`lastFailureAtRef`)
|
||||
- **Abort support:** New navigation aborts in-progress one via `AbortController`
|
||||
- **Highlight-first:** Highlight is set BEFORE scroll (best-effort scroll, guaranteed highlight)
|
||||
|
||||
### Scroll Precedence (ChatHistory.tsx)
|
||||
|
||||
Three scroll systems compete — navigation wins:
|
||||
|
||||
| System | Guard | Priority |
|
||||
|--------|-------|----------|
|
||||
| Navigation scroll | Controller's `executeNavigation` | Highest |
|
||||
| Scroll restore (tab switch) | `!shouldDisableAutoScroll` | Medium |
|
||||
| Auto-scroll to bottom | `disabled: shouldDisableAutoScroll` | Lowest |
|
||||
|
||||
`shouldDisableAutoScroll` is `true` during ANY navigation phase or when `pendingNavigation` exists.
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `src/renderer/hooks/useTabNavigationController.ts` | Unified navigation controller |
|
||||
| `src/renderer/hooks/navigation/utils.ts` | Shared helpers (scroll calc, element lookup, visibility) |
|
||||
| `src/renderer/components/chat/ChatHistory.tsx` | Scroll restore + auto-scroll coordination |
|
||||
| `src/renderer/store/slices/tabSlice.ts` | `enqueueTabNavigation`, `consumeTabNavigation`, `navigateToSession` |
|
||||
| `src/renderer/store/slices/notificationSlice.ts` | `navigateToError` |
|
||||
| `src/renderer/store/slices/sessionDetailSlice.ts` | `fetchSessionDetail` (sets `conversationLoading`) |
|
||||
| `src/renderer/types/tabs.ts` | `TabNavigationRequest` types + factory helpers |
|
||||
|
||||
## Common Bug Patterns
|
||||
|
||||
### 1. Scroll Restore Overrides Navigation
|
||||
|
||||
**Symptom:** Scrolls to target, then snaps back to top/previous position.
|
||||
|
||||
**Root cause:** The scroll restore effect fires after `consumeTabNavigation` clears `pendingNavigation`. If the guard only checks `!pendingNavigation`, it triggers while navigation highlight is still active.
|
||||
|
||||
**Fix pattern:** Guard scroll restore with `!shouldDisableAutoScroll` instead of `!pendingNavigation`. The controller's `shouldDisableAutoScroll` covers the FULL lifecycle (pending → complete), not just while `pendingNavigation` exists.
|
||||
|
||||
**Additional:** Save scroll position when `shouldDisableAutoScroll` transitions true→false (navigation completed) to prevent stale `savedScrollTop` from being restored later.
|
||||
|
||||
```typescript
|
||||
// ChatHistory.tsx — scroll restore effect
|
||||
useEffect(() => {
|
||||
const wasDisabled = prevShouldDisableRef.current;
|
||||
prevShouldDisableRef.current = shouldDisableAutoScroll;
|
||||
// Navigation just completed — save current position, skip restore
|
||||
if (wasDisabled && !shouldDisableAutoScroll && scrollContainerRef.current) {
|
||||
saveScrollPosition(scrollContainerRef.current.scrollTop);
|
||||
return;
|
||||
}
|
||||
if (isThisTabActive && savedScrollTop !== undefined && !conversationLoading && !shouldDisableAutoScroll) {
|
||||
// ... restore logic
|
||||
}
|
||||
}, [isThisTabActive, savedScrollTop, conversationLoading, shouldDisableAutoScroll, saveScrollPosition]);
|
||||
```
|
||||
|
||||
### 2. Redundant `fetchSessionDetail` Unmounts ChatHistory
|
||||
|
||||
**Symptom:** Navigation doesn't scroll at all, or session "reloads" unnecessarily.
|
||||
|
||||
**Root cause:** `navigateToSession` or `navigateToError` calls `fetchSessionDetail` even when the session is already loaded in an existing tab. This sets `conversationLoading: true`, causing ChatHistory to unmount (show loading spinner) and remount — losing scroll container and controller state.
|
||||
|
||||
**Fix pattern:** Only call `fetchSessionDetail` for NEW tabs. For existing tabs, `setActiveTab` already handles the fetch when `sessionChanged` is true.
|
||||
|
||||
```typescript
|
||||
// tabSlice.ts — navigateToSession
|
||||
if (existingTab) {
|
||||
state.setActiveTab(existingTab.id);
|
||||
// NO fetchSessionDetail — setActiveTab handles it
|
||||
} else {
|
||||
state.openTab({ ... });
|
||||
void state.fetchSessionDetail(projectId, sessionId); // Only for new tabs
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Highlight Not Showing (Strict Post-Scroll Gates)
|
||||
|
||||
**Symptom:** Scrolls to correct location but no red/yellow highlight ring appears.
|
||||
|
||||
**Root cause:** `executeErrorNavigation` / `executeSearchNavigation` returns `false` after scroll due to strict gates:
|
||||
- `userInterrupted` — any accidental wheel/touch event during smooth scroll
|
||||
- `isElementVisibleInContainer` — element partially off-screen after centering (tall elements)
|
||||
- Element not found within 600ms timeout
|
||||
|
||||
When `success = false`, `executeNavigation` clears all highlight state (`setHighlightedGroupId(null)`).
|
||||
|
||||
**Fix pattern:** Set highlight BEFORE scroll attempt. Make scroll best-effort. Always return `true` once target group is found.
|
||||
|
||||
```typescript
|
||||
// In executeErrorNavigation:
|
||||
// 1. Find target group
|
||||
// 2. Expand group
|
||||
// 3. SET HIGHLIGHT HERE (before scroll)
|
||||
setHighlightedGroupId(targetGroupId);
|
||||
setIsSearchHighlight(false);
|
||||
if (toolUseId) setCurrentToolUseId(toolUseId);
|
||||
// 4. Best-effort scroll (don't gate highlight on scroll outcome)
|
||||
// 5. Return true (highlight already visible)
|
||||
```
|
||||
|
||||
### 4. Test Trigger Shows No Highlight
|
||||
|
||||
**Symptom:** "Test trigger" creates an error with a timestamp that doesn't match any AI group. Navigation scrolls but nothing is highlighted.
|
||||
|
||||
**Root cause:** Same as #3. The error timestamp doesn't match, so `findAIGroupByTimestamp` falls back to closest/last group. But post-scroll gates prevent the highlight from being applied.
|
||||
|
||||
**Fix:** Same as #3 — highlight-first pattern.
|
||||
|
||||
### 5. `conversationLoading` Race During Tab Switch
|
||||
|
||||
**Symptom:** Navigation queued on tab, but when switching to that tab, loading state causes ChatHistory unmount. Navigation controller state is lost.
|
||||
|
||||
**Root cause:** `fetchSessionDetail` immediately sets `conversationLoading: true`. ChatHistory returns `<ChatHistoryLoadingState />`, unmounting the controller hook.
|
||||
|
||||
**Recovery:** The controller is designed to survive remount — when ChatHistory remounts with `pendingNavigation` still set and `conversationLoading: false`, the detection effect starts fresh navigation. BUT scroll restore can race with it (see #1).
|
||||
|
||||
## Debugging Checklist
|
||||
|
||||
When navigation isn't working:
|
||||
|
||||
1. **Check `pendingNavigation` exists on the tab** — is `enqueueTabNavigation` called?
|
||||
2. **Check `isActiveTab` is true** — controller ignores inactive tabs
|
||||
3. **Check `conversationLoading`** — if true, controller waits in `pending` phase
|
||||
4. **Check `conversation` exists** — if null, controller waits
|
||||
5. **Check timestamp matching** — does `findAIGroupByTimestamp` find the right group?
|
||||
6. **Check element refs** — are `aiGroupRefs` / `chatItemRefs` populated?
|
||||
7. **Check `shouldDisableAutoScroll`** — is scroll restore racing with navigation?
|
||||
8. **Check for double `fetchSessionDetail`** — is ChatHistory unmounting unnecessarily?
|
||||
9. **Check `phase` progression** — is it stuck in `pending` or failing at `scrolling`?
|
||||
|
||||
## Navigation Helper Functions (navigation/utils.ts)
|
||||
|
||||
| Function | Purpose |
|
||||
|----------|---------|
|
||||
| `findAIGroupByTimestamp(items, timestamp)` | Find AI group containing/closest to timestamp |
|
||||
| `findChatItemByTimestamp(items, timestamp)` | Find any chat item by timestamp |
|
||||
| `findAIGroupBySubagentId(items, subagentId)` | Find AI group by subagent ID |
|
||||
| `calculateCenteredScrollTop(element, container, offset)` | Calculate scroll position to center element |
|
||||
| `waitForElementStability(element, timeout, stableFrames)` | Wait for element size to stop changing |
|
||||
| `waitForScrollEnd(container, timeout)` | Wait for smooth scroll to finish |
|
||||
| `isElementVisibleInContainer(element, container, offset)` | Check if element is in viewport |
|
||||
| `findCurrentSearchResultInContainer(container)` | Find `[data-search-result="current"]` element |
|
||||
|
||||
## Factory Helpers (tabs.ts)
|
||||
|
||||
```typescript
|
||||
createErrorNavigationRequest({ errorId, errorTimestamp, toolUseId, lineNumber })
|
||||
createSearchNavigationRequest({ query, messageTimestamp, matchedText })
|
||||
isErrorPayload(request) // type guard
|
||||
isSearchPayload(request) // type guard
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Related test files:
|
||||
- `test/renderer/store/tabSlice.test.ts` — `enqueueTabNavigation` / `consumeTabNavigation`
|
||||
- `test/renderer/store/notificationSlice.test.ts` — `navigateToError` behavior
|
||||
- `test/renderer/hooks/navigationUtils.test.ts` — Navigation utility functions
|
||||
- `test/renderer/hooks/useSearchContextNavigation.test.ts` — Search result finding
|
||||
|
||||
Test patterns:
|
||||
```typescript
|
||||
// Mock crypto for predictable nonces
|
||||
vi.stubGlobal('crypto', { randomUUID: () => `test-uuid-${++counter}` });
|
||||
|
||||
// Verify navigation request shape
|
||||
expect(tab.pendingNavigation?.kind).toBe('error');
|
||||
expect(tab.pendingNavigation?.highlight).toBe('red');
|
||||
|
||||
// Verify nonce uniqueness on repeated clicks
|
||||
store.getState().navigateToError(error);
|
||||
const firstId = store.getState().openTabs[0].pendingNavigation?.id;
|
||||
store.getState().navigateToError(error);
|
||||
const secondId = store.getState().openTabs[0].pendingNavigation?.id;
|
||||
expect(firstId).not.toBe(secondId);
|
||||
```
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
---
|
||||
globs: ["src/renderer/**/*.tsx"]
|
||||
---
|
||||
|
||||
# React Conventions
|
||||
|
||||
## Component Structure
|
||||
- Components in `src/renderer/components/` organized by feature
|
||||
- One component per file, PascalCase naming
|
||||
- Colocate related hooks and utilities
|
||||
|
||||
## State Management (Zustand)
|
||||
```typescript
|
||||
// Slices pattern
|
||||
projects: Project[]
|
||||
selectedProjectId: string | null
|
||||
projectsLoading: boolean
|
||||
projectsError: string | null
|
||||
```
|
||||
|
||||
Each domain slice includes:
|
||||
- Data array or object
|
||||
- Selected/active item ID
|
||||
- Loading state
|
||||
- Error state
|
||||
|
||||
## Hooks
|
||||
- Custom hooks in `src/renderer/hooks/`
|
||||
- Prefix with `use`: `useAutoScrollBottom`, `useTheme`
|
||||
- Keep hooks focused and composable
|
||||
|
||||
## Component Organization
|
||||
```
|
||||
components/
|
||||
├── chat/ # Chat display, items, viewers, SessionContextPanel
|
||||
├── common/ # Shared components (badges, token display)
|
||||
├── dashboard/ # Dashboard views
|
||||
├── layout/ # Layout components (headers, shells)
|
||||
├── notifications/ # Notification panels and badges
|
||||
├── search/ # Search UI and results
|
||||
├── settings/ # Settings pages and controls
|
||||
│ ├── components/ # Reusable setting controls
|
||||
│ ├── hooks/ # Settings-specific hooks
|
||||
│ ├── sections/ # Setting sections
|
||||
│ └── NotificationTriggerSettings/ # Trigger config UI
|
||||
└── sidebar/ # Sidebar navigation
|
||||
```
|
||||
|
||||
## Data Access: Store over Props
|
||||
When data is available in the Zustand store, child components should read it directly via `useStore()` instead of receiving it through props. This avoids unnecessary prop drilling and keeps parent components clean.
|
||||
|
||||
```tsx
|
||||
// Preferred — child reads from store
|
||||
const ProcessesSection = () => {
|
||||
const teamName = useStore((s) => s.selectedTeamName);
|
||||
const data = useStore((s) => s.selectedTeamData);
|
||||
// ...
|
||||
};
|
||||
|
||||
// Avoid — parent drills store data as props
|
||||
<ProcessesSection teamName={teamName} processes={data.processes} members={data.members} />
|
||||
```
|
||||
|
||||
Only pass props when the data is NOT in the store (e.g. local state, computed values, callbacks).
|
||||
|
||||
## Contexts
|
||||
- `contexts/TabUIContext.tsx` - Per-tab UI state isolation
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
---
|
||||
globs: ["**/*.css", "src/renderer/**/*.tsx"]
|
||||
---
|
||||
|
||||
# Tailwind CSS Conventions
|
||||
|
||||
## Theme Architecture
|
||||
Uses CSS custom properties for theme-aware colors defined in `src/renderer/index.css`.
|
||||
|
||||
### Core Surface Colors
|
||||
```css
|
||||
--color-surface: #141416 /* Main background */
|
||||
--color-surface-raised: #27272a /* Elevated surfaces */
|
||||
--color-surface-overlay: #27272a /* Overlays/modals */
|
||||
--color-surface-sidebar: #0f0f11 /* Sidebar background */
|
||||
```
|
||||
|
||||
### Border Colors
|
||||
```css
|
||||
--color-border: rgba(255, 255, 255, 0.05)
|
||||
--color-border-subtle: rgba(255, 255, 255, 0.05)
|
||||
--color-border-emphasis: rgba(255, 255, 255, 0.1)
|
||||
```
|
||||
|
||||
### Text Colors
|
||||
```css
|
||||
--color-text: #fafafa /* Primary text */
|
||||
--color-text-secondary: #a1a1aa /* Secondary text */
|
||||
--color-text-muted: #71717a /* Muted text */
|
||||
```
|
||||
|
||||
## Tailwind Usage
|
||||
Use theme-aware classes that reference CSS variables:
|
||||
```tsx
|
||||
// Preferred - uses CSS variables for theme support
|
||||
<div className="bg-surface text-text border-border">
|
||||
<div className="bg-surface-raised text-text-secondary">
|
||||
|
||||
// Also available via claude-dark namespace
|
||||
<div className="bg-claude-dark-bg text-claude-dark-text">
|
||||
```
|
||||
|
||||
## Additional CSS Variable Categories
|
||||
- Chat bubbles: `--chat-user-*`, `--chat-ai-*`, `--chat-system-*`
|
||||
- Code blocks: `--code-*`, `--syntax-*`, `--inline-code-*`
|
||||
- Diff viewer: `--diff-added-*`, `--diff-removed-*`
|
||||
- Tool blocks: `--tool-call-*`, `--tool-result-*`
|
||||
- Tool items: `--tool-item-name`, `--tool-item-summary`, `--tool-item-muted`, `--tool-item-hover-bg`
|
||||
- Badges: `--badge-*`, `--tag-*`
|
||||
- Search: `--highlight-*`
|
||||
- Scrollbar: `--scrollbar-thumb`, `--scrollbar-thumb-hover`, `--scrollbar-thumb-active`
|
||||
- Prose/Markdown: `--prose-heading`, `--prose-body`, `--prose-link`, `--prose-code-*`, `--prose-pre-*`
|
||||
- Thinking blocks: `--thinking-bg`, `--thinking-border`, `--thinking-text`, `--thinking-content-*`
|
||||
- Output blocks: `--output-bg`, `--output-border`, `--output-text`, `--output-content-border`
|
||||
- Cards/Subagents: `--card-bg`, `--card-border`, `--card-header-*`, `--card-icon-muted`, `--card-separator`
|
||||
- Highlights: `--skill-highlight-*`, `--path-highlight-*`
|
||||
- UI elements: `--interruption-*`, `--warning-*`, `--plan-exit-*`, `--error-highlight-*`, `--kbd-*`, `--context-btn-*`
|
||||
|
||||
## Dark/Light Theme
|
||||
Both themes supported via `:root` and `:root.light` in index.css.
|
||||
Toggle via `useTheme` hook which adds/removes `light` class on root.
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
---
|
||||
globs: ["test/**/*", "**/*.test.ts", "**/*.spec.ts"]
|
||||
---
|
||||
|
||||
# Testing Conventions
|
||||
|
||||
## Test Framework
|
||||
Uses Vitest with `happy-dom` environment. Config in `vitest.config.ts`.
|
||||
|
||||
## Test Commands
|
||||
```bash
|
||||
pnpm test # Run all vitest tests
|
||||
pnpm test:watch # Watch mode
|
||||
pnpm test:coverage # Coverage report
|
||||
pnpm test:coverage:critical # Critical path coverage
|
||||
pnpm test:chunks # Chunk building tests
|
||||
pnpm test:semantic # Semantic step extraction
|
||||
pnpm test:noise # Noise filtering tests
|
||||
pnpm test:task-filtering # Task tool filtering
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
```
|
||||
test/
|
||||
├── main/
|
||||
│ ├── ipc/ # IPC handler tests
|
||||
│ │ ├── configValidation.test.ts
|
||||
│ │ └── guards.test.ts
|
||||
│ ├── services/ # Service tests
|
||||
│ │ ├── analysis/ (ChunkBuilder)
|
||||
│ │ ├── discovery/ (ProjectPathResolver, SessionSearcher)
|
||||
│ │ ├── infrastructure/ (FileWatcher)
|
||||
│ │ └── parsing/ (MessageClassifier, SessionParser)
|
||||
│ └── utils/ # Main process utilities
|
||||
│ ├── jsonl.test.ts
|
||||
│ ├── pathDecoder.test.ts
|
||||
│ ├── pathValidation.test.ts
|
||||
│ ├── regexValidation.test.ts
|
||||
│ └── tokenizer.test.ts
|
||||
├── renderer/
|
||||
│ ├── hooks/ # Hook tests
|
||||
│ │ ├── navigationUtils.test.ts
|
||||
│ │ ├── useAutoScrollBottom.test.ts
|
||||
│ │ ├── useSearchContextNavigation.test.ts
|
||||
│ │ └── useVisibleAIGroup.test.ts
|
||||
│ ├── store/ # Zustand store slices
|
||||
│ │ ├── notificationSlice.test.ts
|
||||
│ │ ├── paneSlice.test.ts
|
||||
│ │ ├── pathResolution.test.ts
|
||||
│ │ ├── sessionSlice.test.ts
|
||||
│ │ ├── tabSlice.test.ts
|
||||
│ │ └── tabUISlice.test.ts
|
||||
│ └── utils/ # Renderer utilities
|
||||
│ ├── claudeMdTracker.test.ts
|
||||
│ ├── dateGrouping.test.ts
|
||||
│ ├── formatters.test.ts
|
||||
│ └── pathUtils.test.ts
|
||||
├── shared/
|
||||
│ └── utils/ # Shared utilities
|
||||
│ ├── markdownSearchRendererAlignment.test.ts
|
||||
│ ├── markdownTextSearch.test.ts
|
||||
│ ├── modelParser.test.ts
|
||||
│ └── tokenFormatting.test.ts
|
||||
├── mocks/ # Test fixtures and mocks
|
||||
└── setup.ts # Test setup/config
|
||||
```
|
||||
|
||||
## Files to Test After Changes
|
||||
- `services/analysis/ChunkBuilder.ts` - Chunk building logic
|
||||
- `services/parsing/SessionParser.ts` - JSONL parsing
|
||||
- `services/parsing/MessageClassifier.ts` - Message classification
|
||||
- Store slices in `src/renderer/store/slices/`
|
||||
- Utility functions in `*/utils/`
|
||||
|
||||
## Test Data
|
||||
Test fixtures use real JSONL session data from `~/.claude/projects/`.
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"alwaysThinkingEnabled": true,
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Edit",
|
||||
"Bash",
|
||||
"ReadFile(*)",
|
||||
"Web Search",
|
||||
"WebSearch",
|
||||
"WebFetch",
|
||||
"Fetch"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -41,9 +41,7 @@ yarn-error.log*
|
|||
package-lock.json
|
||||
notification_example/
|
||||
temp/
|
||||
.claude/*.local.json
|
||||
.claude/agent-memory/*
|
||||
.claude/worktrees/
|
||||
.claude/
|
||||
|
||||
|
||||
eslint-fix/
|
||||
|
|
|
|||
Loading…
Reference in a new issue