2 Commits

Author SHA1 Message Date
Roberto Musso
9c07d3195f first color revision 2026-02-27 00:15:45 +01:00
Roberto Musso
8f1ba08e54 delete unused file 2026-02-26 23:41:25 +01:00
6 changed files with 88 additions and 1622 deletions

4
.gitignore vendored
View File

@@ -90,3 +90,7 @@ typings/
# Electron-Forge
out/
# local config files
.claude/
.vscode/

File diff suppressed because it is too large Load Diff

View File

@@ -1,105 +0,0 @@
# Ralph Agent Instructions
You are an autonomous coding agent working on a software project.
## Your Task
1. Read the full app PRD at `prd-main.md` (in the same directory as this file)
2. Read the PRD at `prd.json` (in the same directory as this file)
3. Read the progress log at `progress.txt` (check Codebase Patterns section first)
4. Check you're on the correct branch from PRD `branchName`. If not, check it out or create from main.
5. Pick the **highest priority** user story where `passes: false`
6. Implement that single user story
7. Run quality checks (e.g., typecheck, lint, test - use whatever your project requires)
8. Update CLAUDE.md files if you discover reusable patterns (see below)
9. If checks pass, commit ALL changes with message: `feat: [Story ID] - [Story Title]`
10. Update the PRD to set `passes: true` for the completed story
11. Append your progress to `progress.txt`
## Progress Report Format
APPEND to progress.txt (never replace, always append):
```
## [Date/Time] - [Story ID]
- What was implemented
- Files changed
- **Learnings for future iterations:**
- Patterns discovered (e.g., "this codebase uses X for Y")
- Gotchas encountered (e.g., "don't forget to update Z when changing W")
- Useful context (e.g., "the evaluation panel is in component X")
---
```
The learnings section is critical - it helps future iterations avoid repeating mistakes and understand the codebase better.
## Consolidate Patterns
If you discover a **reusable pattern** that future iterations should know, add it to the `## Codebase Patterns` section at the TOP of progress.txt (create it if it doesn't exist). This section should consolidate the most important learnings:
```
## Codebase Patterns
- Example: Use `sql<number>` template for aggregations
- Example: Always use `IF NOT EXISTS` for migrations
- Example: Export types from actions.ts for UI components
```
Only add patterns that are **general and reusable**, not story-specific details.
## Update CLAUDE.md Files
Before committing, check if any edited files have learnings worth preserving in nearby CLAUDE.md files:
1. **Identify directories with edited files** - Look at which directories you modified
2. **Check for existing CLAUDE.md** - Look for CLAUDE.md in those directories or parent directories
3. **Add valuable learnings** - If you discovered something future developers/agents should know:
- API patterns or conventions specific to that module
- Gotchas or non-obvious requirements
- Dependencies between files
- Testing approaches for that area
- Configuration or environment requirements
**Examples of good CLAUDE.md additions:**
- "When modifying X, also update Y to keep them in sync"
- "This module uses pattern Z for all API calls"
- "Tests require the dev server running on PORT 3000"
- "Field names must match the template exactly"
**Do NOT add:**
- Story-specific implementation details
- Temporary debugging notes
- Information already in progress.txt
Only update CLAUDE.md if you have **genuinely reusable knowledge** that would help future work in that directory.
## Quality Requirements
- ALL commits must pass your project's quality checks (typecheck, lint, test)
- Do NOT commit broken code
- Keep changes focused and minimal
- Follow existing code patterns
## Browser Testing (If Available)
For any story that changes UI, verify it works in the browser if you have browser testing tools configured (e.g., via MCP):
1. Navigate to the relevant page
2. Verify the UI changes work as expected
3. Take a screenshot if helpful for the progress log
If no browser tools are available, note in your progress report that manual browser verification is needed.
## Stop Condition
After completing a user story, check if ALL stories have `passes: true`.
If ALL stories are complete and passing, reply with:
<promise>COMPLETE</promise>
If there are still stories with `passes: false`, end your response normally (another iteration will pick up the next story).
## Important
- Work on ONE story per iteration
- Commit frequently
- Keep CI green
- Read the Codebase Patterns section in progress.txt before starting

View File

@@ -1,113 +0,0 @@
#!/bin/bash
# Ralph Wiggum - Long-running AI agent loop
# Usage: ./ralph.sh [--tool amp|claude] [max_iterations]
set -e
# Parse arguments
TOOL="claude" # Default to claude for backwards compatibility
MAX_ITERATIONS=10
while [[ $# -gt 0 ]]; do
case $1 in
--tool)
TOOL="$2"
shift 2
;;
--tool=*)
TOOL="${1#*=}"
shift
;;
*)
# Assume it's max_iterations if it's a number
if [[ "$1" =~ ^[0-9]+$ ]]; then
MAX_ITERATIONS="$1"
fi
shift
;;
esac
done
# Validate tool choice
if [[ "$TOOL" != "amp" && "$TOOL" != "claude" ]]; then
echo "Error: Invalid tool '$TOOL'. Must be 'amp' or 'claude'."
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PRD_FILE="$SCRIPT_DIR/prd.json"
PROGRESS_FILE="$SCRIPT_DIR/progress.txt"
ARCHIVE_DIR="$SCRIPT_DIR/archive"
LAST_BRANCH_FILE="$SCRIPT_DIR/.last-branch"
# Archive previous run if branch changed
if [ -f "$PRD_FILE" ] && [ -f "$LAST_BRANCH_FILE" ]; then
CURRENT_BRANCH=$(jq -r '.branchName // empty' "$PRD_FILE" 2>/dev/null || echo "")
LAST_BRANCH=$(cat "$LAST_BRANCH_FILE" 2>/dev/null || echo "")
if [ -n "$CURRENT_BRANCH" ] && [ -n "$LAST_BRANCH" ] && [ "$CURRENT_BRANCH" != "$LAST_BRANCH" ]; then
# Archive the previous run
DATE=$(date +%Y-%m-%d)
# Strip "ralph/" prefix from branch name for folder
FOLDER_NAME=$(echo "$LAST_BRANCH" | sed 's|^ralph/||')
ARCHIVE_FOLDER="$ARCHIVE_DIR/$DATE-$FOLDER_NAME"
echo "Archiving previous run: $LAST_BRANCH"
mkdir -p "$ARCHIVE_FOLDER"
[ -f "$PRD_FILE" ] && cp "$PRD_FILE" "$ARCHIVE_FOLDER/"
[ -f "$PROGRESS_FILE" ] && cp "$PROGRESS_FILE" "$ARCHIVE_FOLDER/"
echo " Archived to: $ARCHIVE_FOLDER"
# Reset progress file for new run
echo "# Ralph Progress Log" > "$PROGRESS_FILE"
echo "Started: $(date)" >> "$PROGRESS_FILE"
echo "---" >> "$PROGRESS_FILE"
fi
fi
# Track current branch
if [ -f "$PRD_FILE" ]; then
CURRENT_BRANCH=$(jq -r '.branchName // empty' "$PRD_FILE" 2>/dev/null || echo "")
if [ -n "$CURRENT_BRANCH" ]; then
echo "$CURRENT_BRANCH" > "$LAST_BRANCH_FILE"
fi
fi
# Initialize progress file if it doesn't exist
if [ ! -f "$PROGRESS_FILE" ]; then
echo "# Ralph Progress Log" > "$PROGRESS_FILE"
echo "Started: $(date)" >> "$PROGRESS_FILE"
echo "---" >> "$PROGRESS_FILE"
fi
echo "Starting Ralph - Tool: $TOOL - Max iterations: $MAX_ITERATIONS"
for i in $(seq 1 $MAX_ITERATIONS); do
echo ""
echo "==============================================================="
echo " Ralph Iteration $i of $MAX_ITERATIONS ($TOOL)"
echo "==============================================================="
# Run the selected tool with the ralph prompt
if [[ "$TOOL" == "amp" ]]; then
OUTPUT=$(cat "$SCRIPT_DIR/prompt.md" | amp --dangerously-allow-all 2>&1 | tee /dev/stderr) || true
else
# Claude Code: use --dangerously-skip-permissions for autonomous operation, --print for output
OUTPUT=$(claude --dangerously-skip-permissions --print < "$SCRIPT_DIR/CLAUDE.md" 2>&1 | tee /dev/stderr) || true
fi
# Check for completion signal
if echo "$OUTPUT" | grep -q "<promise>COMPLETE</promise>"; then
echo ""
echo "Ralph completed all tasks!"
echo "Completed at iteration $i of $MAX_ITERATIONS"
exit 0
fi
echo "Iteration $i complete. Continuing..."
sleep 2
done
echo ""
echo "Ralph reached max iterations ($MAX_ITERATIONS) without completing all tasks."
echo "Check $PROGRESS_FILE for status."
exit 1

View File

@@ -50,73 +50,113 @@
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
/* #f4edf3 - Light Pinkish-White Canvas */
--background: oklch(0.945 0.012 328.5);
/* #040404 - Almost Black Text */
--foreground: oklch(0.145 0 0);
--card: oklch(0.945 0.012 328.5);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(0.945 0.012 328.5);
--popover-foreground: oklch(0.145 0 0);
/* #fbc881 - Golden Yellow Accent */
--primary: oklch(0.838 0.117 76.8);
--primary-foreground: oklch(0.145 0 0);
/* #8a8ea9 - Slate Blue/Gray */
--secondary: oklch(0.627 0.041 274.5);
--secondary-foreground: oklch(0.945 0.012 328.5);
/* #c8c3cd - Light Gray/Purple */
--muted: oklch(0.811 0.014 300.2);
--muted-foreground: oklch(0.627 0.041 274.5);
--accent: oklch(0.811 0.014 300.2);
--accent-foreground: oklch(0.145 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
--border: oklch(0.811 0.014 300.2);
--input: oklch(0.811 0.014 300.2);
--ring: oklch(0.838 0.117 76.8);
/* Kept your original chart colors */
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.21 0.006 285.885);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
/* Sidebar uses the custom palette */
--sidebar: oklch(0.945 0.012 328.5);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.838 0.117 76.8);
--sidebar-primary-foreground: oklch(0.145 0 0);
--sidebar-accent: oklch(0.811 0.014 300.2);
--sidebar-accent-foreground: oklch(0.145 0 0);
--sidebar-border: oklch(0.811 0.014 300.2);
--sidebar-ring: oklch(0.838 0.117 76.8);
}
.dark {
--background: oklch(0.141 0.005 285.823);
/* #0c0c0c - Deepest black for the main canvas */
--background: oklch(0.15 0 0);
/* #fbfbfb - Crisp white for primary text */
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
/* Cards use the main background but are defined by borders */
--card: oklch(0.15 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover: oklch(0.15 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
/* #fbfbfb - Primary actions (like the active white circle menu item) */
--primary: oklch(0.985 0 0);
/* #0c0c0c - Dark text/icons inside primary buttons */
--primary-foreground: oklch(0.15 0 0);
/* #323232 - Dark gray for secondary surfaces and button backgrounds */
--secondary: oklch(0.335 0 0);
/* #fbfbfb - White text on secondary surfaces */
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
/* #323232 - Dark gray for muted backgrounds */
--muted: oklch(0.335 0 0);
/* #77797b - Mid gray for muted/secondary text (like "ELEVATE YOUR...") */
--muted-foreground: oklch(0.555 0 0);
/* #323232 - Hover states */
--accent: oklch(0.335 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--destructive: oklch(0.704 0.191 22.216); /* Kept original dark red */
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
/* #323232 - Distinct dark gray borders for the cards/panels */
--border: oklch(0.335 0 0);
--input: oklch(0.335 0 0);
/* #bab7ba - Lighter gray for focus rings to stand out against dark borders */
--ring: oklch(0.765 0 0);
/* Kept your original chart colors */
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.21 0.006 285.885);
/* Sidebar mapped to the new sleek dark palette */
--sidebar: oklch(0.15 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-primary: oklch(0.985 0 0);
--sidebar-primary-foreground: oklch(0.15 0 0);
--sidebar-accent: oklch(0.335 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
--sidebar-border: oklch(0.335 0 0);
--sidebar-ring: oklch(0.765 0 0);
}
@layer base {

View File

@@ -1,130 +0,0 @@
import { useState, useCallback, useRef } from 'react';
import { trpc } from '@/lib/trpc';
export interface ChatMessage {
id: string;
role: 'user' | 'assistant';
content: string;
error?: boolean;
}
export interface ChatContext {
type: 'global' | 'project';
projectId?: string;
uiContext?: string;
}
export interface UseAIChatReturn {
messages: ChatMessage[];
input: string;
setInput: (v: string) => void;
isStreaming: boolean;
streamingContent: string;
handleSend: (overrideMessage?: string, overrideContext?: ChatContext) => void;
clearMessages: () => void;
}
export function useAIChat(defaultContext: ChatContext): UseAIChatReturn {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [input, setInput] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const [streamingContent, setStreamingContent] = useState('');
const streamingContentRef = useRef('');
const chatMutation = trpc.ai.chat.useMutation();
const clearMessages = useCallback(() => {
setMessages([]);
setStreamingContent('');
streamingContentRef.current = '';
}, []);
const handleSend = useCallback(
(overrideMessage?: string, overrideContext?: ChatContext) => {
const trimmed = (overrideMessage ?? input).trim();
if (!trimmed || isStreaming) return;
const userMsg: ChatMessage = {
id: crypto.randomUUID(),
role: 'user',
content: trimmed,
};
setMessages((prev) => [...prev, userMsg]);
if (!overrideMessage) setInput('');
setIsStreaming(true);
setStreamingContent('');
streamingContentRef.current = '';
const unsubscribe = window.electronAI.onStreamChunk(({ token, done }) => {
if (done) {
const finalContent = streamingContentRef.current;
setMessages((prev) => [
...prev,
{ id: crypto.randomUUID(), role: 'assistant', content: finalContent },
]);
setStreamingContent('');
streamingContentRef.current = '';
setIsStreaming(false);
unsubscribe();
return;
}
streamingContentRef.current += token;
setStreamingContent(streamingContentRef.current);
});
const ctx = overrideContext ?? defaultContext;
chatMutation.mutate(
{
message: trimmed,
context: {
type: ctx.type,
...(ctx.type === 'project' && ctx.projectId ? { projectId: ctx.projectId } : {}),
...(ctx.uiContext ? { uiContext: ctx.uiContext } : {}),
},
},
{
onSuccess: (data) => {
if (data.error) {
unsubscribe();
setMessages((prev) => [
...prev,
{ id: crypto.randomUUID(), role: 'assistant', content: data.error!, error: true },
]);
setStreamingContent('');
streamingContentRef.current = '';
setIsStreaming(false);
}
},
onError: (err) => {
unsubscribe();
setMessages((prev) => [
...prev,
{
id: crypto.randomUUID(),
role: 'assistant',
content: err.message || 'An unexpected error occurred.',
error: true,
},
]);
setStreamingContent('');
streamingContentRef.current = '';
setIsStreaming(false);
},
},
);
},
[input, isStreaming, defaultContext, chatMutation],
);
return {
messages,
input,
setInput,
isStreaming,
streamingContent,
handleSend,
clearMessages,
};
}