diff --git a/.gitignore b/.gitignore index c9e9fdf..fde5cf9 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ tools/anaf-monitor/versions.json tools/anaf-monitor/snapshots/ tools/anaf-monitor/monitor.log workspace/ + +# Claude Code session handoff +.claude/HANDOFF.md diff --git a/AGENTS.md b/AGENTS.md index dacf9ab..8635f1e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Model Selection -**Default: Sonet** +**Default: Sonnet** ** Pentru urmatoarele sarcini, foloseste Haiku** - Routine tasks, file checks, simple commands, status @@ -101,7 +101,7 @@ When I receive errors, bugs, or new feature requests: - **Fii selectiv** cu integrările externe (trade-off: capability vs risk) ### Daily Security Audit (Cron 09:30) -- Verifică: agents.md, soul.md, user.md, heartbeat.md, tools.md +- Verifică: agents.md, soul.md, user.md, identity.md, heartbeat.md, tools.md, cron-jobs.md, infrastructure.md - Caută: info outdated, reguli conflictuale, workflow-uri nedocumentate - Propune cleanup în #echo-work diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..82e0df7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,87 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview + +This is a multi-project workspace for **Echo**, a personal AI assistant ecosystem owned by Marius. It contains three main components: a web dashboard, Python utility tools, and Antfarm (an agent workflow orchestration CLI). + +**Language:** Romanian is used extensively in docs, variable names, and comments. Marius prefers no emojis in conversation. + +## Project Structure + +- **dashboard/** — Web-based task/habit tracking SPA (vanilla JS + Python backend via `api.py`) +- **antfarm/** — Multi-agent workflow orchestration CLI (TypeScript + Node.js + SQLite) +- **tools/** — Python utility scripts (email, calendar, git, YouTube, ANAF monitor, Ralph workflow) +- **memory/** — Knowledge base and daily notes (`memory/YYYY-MM-DD.md`, `memory/kb/`) +- **skills/** — Agent skill definitions + +## Build & Run Commands + +### Antfarm (TypeScript) +```bash +cd antfarm && npm run build # tsc + copy HTML + chmod +cd antfarm && npm start # node dist/cli/cli.js +antfarm install # Install all bundled workflows +antfarm workflow run # Start a workflow run +antfarm dashboard # Web dashboard on port 3333 +``` +- Requires Node.js >= 22, ES modules (`"type": "module"`) +- TypeScript strict mode, target ES2022, module NodeNext +- No linter or formatter configured + +### Dashboard (Python) +```bash +python3 dashboard/api.py # Start HTTP server +pytest dashboard/tests/ # Run all dashboard tests +pytest dashboard/tests/test_habits_api.py # Run a single test file +``` + +### Tools (Python) +```bash +python3 tools/youtube_subs.py URL [lang] +python3 tools/email_send.py "dest" "subject" "body" +python3 tools/email_process.py [--save|--all] +python3 tools/calendar_check.py [today|week|travel] +python3 tools/git_commit.py --push +python3 tools/ralph_workflow.py # Autonomous code generation +``` + +## Architecture + +### Antfarm — Multi-Agent Workflows +- **Workflow pattern:** YAML-defined pipelines where specialized agents (planner, developer, verifier, tester, reviewer) execute steps sequentially +- **Fresh context per step:** Each agent runs in a clean session — no shared context window +- **State in SQLite:** `~/.openclaw/antfarm/antfarm.db` with WAL mode; tables: `runs`, `steps`, `stories` +- **Cron-based polling:** Agents poll for work at configurable intervals (120s–300s) +- **Tool-gating by role:** agents have restricted tool access (e.g., `verification` role = read + exec, NO write) +- **Bundled workflows:** `feature-dev` (7 agents), `bug-fix` (6 agents), `security-audit` (7 agents) +- **Key source files:** `src/cli/cli.ts` (entry), `src/db.ts` (SQLite), `src/installer/install.ts` (workflow provisioning), `src/installer/workflow-spec.ts` (YAML parsing), `src/installer/step-ops.ts` (step claim/complete/fail), `src/server/dashboard.ts` (HTTP API) + +### Dashboard — Habit Tracker & Task Board +- **Single-page app** with swipe navigation between pages (index, habits, notes, files, workspace) +- **Backend:** Python `SimpleHTTPRequestHandler` with `/api/` routing in `dashboard/api.py` +- **Data:** JSON files (`habits.json`, `todos.json`, `tasks.json`, `status.json`) +- **Frontend:** Vanilla JS + CSS with Lucide icons, design tokens for light/dark themes +- **Served over Tailscale:** `https://moltbot.tailf7372d.ts.net/echo/` + +### Ralph — Autonomous Code Generation +- Opus generates PRD/stories, Sonnet implements them +- `tools/ralph_prd_generator.py` → PRD + `prd.json` +- `tools/ralph_workflow.py` → launches the full loop +- Projects go in `~/workspace/` + +## Key Configuration + +- **AGENTS.md** — Agent behavior rules, model selection (Haiku/Sonnet/Opus), security policies +- **USER.md** — Marius's profile, preferences, and 80/20 work style +- **TOOLS.md** — Available tools with exact CLI invocations +- **antfarm/workflows/{id}/workflow.yml** — YAML workflow definitions +- **antfarm/agents/shared/** — Shared agent definitions (setup, verifier, pr) + +## Conventions + +- **Model selection:** Opus for planning/architecture, Sonnet for implementation/coding, Haiku for routine tasks +- **80/20 rule:** Minimal effort, maximum results — avoid over-engineering +- **Security:** Never store secrets in code; use `.env` files; `trash` over `rm`; confirm destructive actions +- **Git:** Main branch is `master`; remote is `gitea.romfast.ro/romfast/clawd` diff --git a/FEATURE_PDF_DOWNLOAD.md b/FEATURE_PDF_DOWNLOAD.md deleted file mode 100644 index 6c53bf2..0000000 --- a/FEATURE_PDF_DOWNLOAD.md +++ /dev/null @@ -1,206 +0,0 @@ -# PDF Download Feature - Implementation Complete ✅ - -## Overview -Added a "Download PDF" button to the Files Dashboard that converts markdown files to PDF and triggers a download. - -## What Was Changed - -### 1. Frontend (`/home/moltbot/clawd/dashboard/files.html`) - -#### Added Library -- **Line 4**: Included `html2pdf.js` from CDN - ```html - - ``` - - No system dependencies needed (pure JavaScript) - - Works client-side in browser - - 54KB minified, ~16KB gzipped - -#### Added Button (Line 226) -```html - -``` -- Placed next to Preview button in editor header -- Only visible when markdown (.md) files are open -- Uses download icon from Lucide - -#### Added JavaScript Function (after `toggleDiff`) -```javascript -function downloadPDF() { - // Validates file is markdown - // Renders preview HTML - // Configures PDF format (A4 portrait, margins) - // Triggers browser download with original filename -} -``` - -#### Updated `openFile()` Function -- Shows download button only for `.md` files (same as preview button) -- `document.getElementById('downloadPdfBtn').style.display = isMarkdown ? 'flex' : 'none';` - -## How It Works - -### User Flow -1. Browse to Files dashboard: `https://moltbot.tailf7372d.ts.net/echo/files.html` -2. Navigate to folder: `memory/kb/projects/grup-sprijin/biblioteca/` -3. Click any `.md` file (e.g., `fisa-2026-02-05-ancorare-oglinda.md`) -4. Click "📥" (download) button next to the eye (preview) button -5. Browser downloads PDF with name: `fisa-2026-02-05-ancorare-oglinda.pdf` - -### Technical Flow -1. **Button click** → `downloadPDF()` function triggered -2. **Validation** → Check file is `.md` and one is open -3. **Get HTML** → Clone the already-rendered markdown preview -4. **Configure PDF** → Set A4 format, margins, quality -5. **Generate** → html2pdf.js converts HTML to PDF in browser -6. **Download** → Browser's download mechanism saves to user's Downloads folder - -## Features - -✅ **Client-side conversion** - No server load, fast, works offline -✅ **Preserves markdown formatting** - Headers, lists, emphasis, blockquotes, code blocks -✅ **High quality output** - 2x scale canvas, JPEG quality 0.98 -✅ **Proper filename** - Uses original filename with `.pdf` extension -✅ **A4 paper format** - Standard European/international paper size -✅ **Margin control** - 10mm margins for printing -✅ **Status feedback** - Shows "Se pregătește PDF..." then "PDF descărcat: filename.pdf" -✅ **Error handling** - Validates file type and provides helpful error messages - -## Tested Scenarios - -### ✅ Test Case 1: Basic Markdown File -- **File**: `memory/kb/projects/grup-sprijin/biblioteca/fisa-2026-02-05-ancorare-oglinda.md` -- **Format**: Headers, paragraphs, lists, step-by-step instructions -- **Expected**: PDF with proper formatting -- **Status**: IMPLEMENTED - -### ✅ Test Case 2: Button Visibility -- **Scenario**: Open non-markdown file -- **Expected**: Download button hidden -- **Status**: IMPLEMENTED (controlled by `isMarkdown` check in `openFile()`) - -### ✅ Test Case 3: Error Handling -- **Scenario**: Click download without file open -- **Expected**: Shows error message -- **Status**: IMPLEMENTED (validation in `downloadPDF()`) - -## Browser Compatibility - -| Browser | Status | Notes | -|---------|--------|-------| -| Chrome/Chromium | ✅ Full support | Primary target | -| Firefox | ✅ Full support | Excellent compatibility | -| Safari | ✅ Full support | Works great | -| Edge | ✅ Full support | Based on Chromium | - -## File Structure - -``` -/home/moltbot/clawd/ -├── dashboard/ -│ ├── files.html (MODIFIED - Added PDF button + function) -│ ├── api.py (unchanged - no backend needed) -│ └── common.css (unchanged - button uses existing styles) -└── FEATURE_PDF_DOWNLOAD.md (NEW - this file) -``` - -## Dependencies - -- **html2pdf.js v0.10.1** - CDN hosted, no installation needed -- **marked.js** - Already present in project (markdown rendering) -- **Lucide icons** - Already present in project (download icon) - -## Performance - -- **Download button display**: < 1ms (CSS toggle) -- **PDF generation**: 2-5 seconds for typical document (depending on complexity) -- **File size**: Typically 50-200KB for a 2-3 page document - -## Limitations & Future Improvements - -⚠️ **Current Limitations:** -- PDF styling is basic (white background, standard fonts) -- Complex CSS from theme not carried over to PDF -- Very large markdown files (>50KB) may take longer to render - -📝 **Future Enhancements (if needed):** -- Add custom CSS for PDF styling (colors, fonts, branding) -- Support for other formats (txt, html) if time permits -- Progress bar for large documents -- Options dialog (page orientation, margins, quality) -- Batch download multiple files - -## How to Use - -### For Marius -1. Open Files dashboard: `https://moltbot.tailf7372d.ts.net/echo/files.html` -2. Navigate: `memory/kb/projects/grup-sprijin/biblioteca/` -3. Click any `.md` file -4. Click the download button (📥 icon next to eye icon) -5. PDF saves to your **Downloads** folder - -### For Group "Sprijin" Users -You can now easily share and print activity sheets: -- **Export for printing**: Download PDF and print locally -- **Share with others**: Email/send PDF file -- **Archive**: Keep PDF copies of session materials - -## Testing Instructions - -To test the feature: - -```bash -# 1. Navigate to files dashboard -https://moltbot.tailf7372d.ts.net/echo/files.html - -# 2. Go to test file location -Click: memory → kb → projects → grup-sprijin → biblioteca - -# 3. Open test file -Click: fisa-2026-02-05-ancorare-oglinda.md - -# 4. Verify button shows -Look for 📥 icon next to 👁️ (preview) button - -# 5. Download PDF -Click 📥 button - -# 6. Check Downloads folder -File should appear: fisa-2026-02-05-ancorare-oglinda.pdf -``` - -## Implementation Notes - -- **No backend changes needed** - Feature is 100% client-side -- **No additional packages** - Uses CDN-hosted library -- **Backward compatible** - Doesn't affect existing functionality -- **Responsive** - Button adapts to different screen sizes -- **Accessible** - Includes title attribute for tooltips - -## Author Notes - -This is a lightweight, user-friendly implementation that: -- Requires no system dependencies -- Works immediately in any modern browser -- Preserves markdown formatting -- Provides good UX with status feedback -- Can be extended later if needed - -The html2pdf.js library was chosen because: -1. ✅ Works client-side (no server load) -2. ✅ CDN hosted (no installation) -3. ✅ Good markdown → PDF conversion -4. ✅ Reliable browser support -5. ✅ Actively maintained - -## Status: ✅ COMPLETE & READY TO USE - -All acceptance criteria met: -- ✅ Button visible in preview panel -- ✅ Works for .md files -- ✅ Downloads with correct filename -- ✅ Preserves markdown formatting -- ✅ Works in Firefox/Chrome -- ✅ User gets proper feedback diff --git a/TEST_PDF_FEATURE.html b/TEST_PDF_FEATURE.html deleted file mode 100644 index 62f09a1..0000000 --- a/TEST_PDF_FEATURE.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - PDF Download Feature Test - - - - - -
-

📥 PDF Download Feature - Test Suite

- -
- ✅ Test 1: Libraries Loaded
- html2pdf.js: Checking...
- marked.js: Checking... -
- -
- ✅ Test 2: Markdown Rendering
- -
-
- -
- ✅ Test 3: PDF Generation
- -
-
- -
- ✅ Test 4: Full Workflow
- -
-
- -
- Test Status:
-
Ready to run tests...
-
- -

Test Markdown Content:

-
# Fișă Întâlnire Grup Sprijin
-
-**Data:** Joi, 5 februarie 2026, ora 18:00
-**Tema:** Ancorare emoții pozitive & Oglinda celorlalți
-
-## 1. Check-in (15-20 min)
-
-**Întrebare de deschidere:**
-- Ce s-a întâmplat în ultimele două săptămâni?
-- Ce emoții ai avut?
-
-## 2. Exercițiu principal: Ancorarea emoțiilor
-
-**Scop:** Să învățăm să accesăm o emoție pozitivă.
-
-### Pași pentru exercițiu:
-
-1. **Alege emoția** (2 min)
-   - Ce emoție ți-ai dori să poți accesa mai ușor?
-
-2. **Găsește momentul** (5 min)
-   - Gândește-te la un moment din viața ta
-
-
- - - - diff --git a/analyze-mobile-menu.js b/analyze-mobile-menu.js deleted file mode 100644 index 4e58e9f..0000000 --- a/analyze-mobile-menu.js +++ /dev/null @@ -1,142 +0,0 @@ -// Analyze files.html mobile menu logic - -const testFiles = [ - { name: 'AGENTS.md', status: 'M', expectPreview: true, expectPDF: true, expectGitDiff: true }, - { name: 'FEATURE_PDF_DOWNLOAD.md', status: '??', expectPreview: true, expectPDF: true, expectGitDiff: false }, - { name: 'TOOLS.md', status: 'M', expectPreview: true, expectPDF: true, expectGitDiff: true }, - { name: 'dashboard/api.py', status: 'M', expectPreview: false, expectPDF: false, expectGitDiff: true }, - { name: 'memory/2026-02-05.md', status: '??', expectPreview: true, expectPDF: true, expectGitDiff: false } -]; - -console.log('========================================'); -console.log('MOBILE MENU LOGIC ANALYSIS'); -console.log('========================================\n'); - -console.log('Based on files.html openFile() function:\n'); -console.log('Lines 1256-1290 (approx):'); -console.log(' const isMarkdown = path.endsWith(\'.md\');'); -console.log(' previewBtn.style.display = isMarkdown ? \'flex\' : \'none\';'); -console.log(' downloadPdfBtn.style.display = isMarkdown ? \'flex\' : \'none\';'); -console.log(' previewMenuItem.classList.toggle(\'hidden\', !isMarkdown);'); -console.log(' downloadPdfMenuItem.classList.toggle(\'hidden\', !isMarkdown);'); -console.log(''); -console.log(' const hasGitChanges = !!getGitStatusForPath(path);'); -console.log(' diffBtn.style.display = hasGitChanges ? \'flex\' : \'none\';'); -console.log(' diffMenuItem.classList.remove(\'hidden\');'); -console.log(' diffMenuItem.disabled = !hasGitChanges;'); -console.log(''); -console.log('Mobile CSS (lines 813-825):'); -console.log(' @media (max-width: 768px) {'); -console.log(' #previewBtn, #downloadPdfBtn, #diffBtn, #reloadBtn {'); -console.log(' display: none !important;'); -console.log(' }'); -console.log(' .editor-menu-mobile {'); -console.log(' display: flex !important;'); -console.log(' }'); -console.log(' }'); -console.log('\n========================================'); -console.log('EXPECTED BEHAVIOR ON MOBILE (375px)'); -console.log('========================================\n'); - -testFiles.forEach(file => { - console.log(`\n${file.name} (git status: ${file.status})`); - console.log('─'.repeat(50)); - - const isMarkdown = file.name.endsWith('.md'); - const hasGitChanges = file.status !== '??'; // Untracked files don't have git diff - - console.log(` Is Markdown: ${isMarkdown}`); - console.log(` Has Git Changes: ${hasGitChanges} (status: ${file.status})`); - console.log(''); - console.log(' Hamburger Menu (⋮): VISIBLE (always on mobile)'); - console.log(''); - console.log(' Menu Items:'); - console.log(` Preview: ${isMarkdown ? 'VISIBLE' : 'HIDDEN (not markdown)'}`); - console.log(` Download PDF: ${isMarkdown ? 'VISIBLE' : 'HIDDEN (not markdown)'}`); - console.log(` Git Diff: ${hasGitChanges ? 'VISIBLE' : 'VISIBLE but DISABLED (no git changes)'}`); - console.log(` Reload: VISIBLE (always)`); - console.log(''); - - // Check expectations - const issues = []; - - if (file.expectPreview && !isMarkdown) { - issues.push('❌ Expected preview but file is not markdown'); - } else if (!file.expectPreview && isMarkdown) { - issues.push('❌ Did not expect preview but file is markdown'); - } - - if (file.expectPDF && !isMarkdown) { - issues.push('❌ Expected PDF but file is not markdown'); - } else if (!file.expectPDF && isMarkdown) { - issues.push('❌ Did not expect PDF but file is markdown'); - } - - if (file.expectGitDiff && !hasGitChanges) { - issues.push('❌ Expected git diff but file has no git changes (status: ??)'); - } else if (!file.expectGitDiff && hasGitChanges) { - issues.push('❌ Did not expect git diff but file has git changes'); - } - - if (issues.length > 0) { - console.log(' 🔴 ISSUES:'); - issues.forEach(issue => console.log(` ${issue}`)); - } else { - console.log(' ✅ Logic matches expectations'); - } -}); - -console.log('\n\n========================================'); -console.log('CODE BEHAVIOR ANALYSIS'); -console.log('========================================\n'); - -console.log('✅ CORRECT BEHAVIOR:'); -console.log(' - Hamburger menu (⋮) always visible on mobile'); -console.log(' - Preview/PDF menu items: shown only for .md files'); -console.log(' - Git Diff menu item: always shown but disabled for ?? files'); -console.log(' - Desktop buttons hidden on mobile with !important'); -console.log(''); - -console.log('⚠️ POTENTIAL ISSUE:'); -console.log(' The code shows diffMenuItem always visible but disabled for files'); -console.log(' with no git changes. This is technically correct but could be'); -console.log(' confusing for users (they see a disabled option).'); -console.log(''); -console.log(' Better approach would be:'); -console.log(' diffMenuItem.classList.toggle(\'hidden\', !hasGitChanges);'); -console.log(' instead of:'); -console.log(' diffMenuItem.classList.remove(\'hidden\');'); -console.log(' diffMenuItem.disabled = !hasGitChanges;'); -console.log(''); - -console.log('📝 RECOMMENDATION:'); -console.log(' Change line ~1288 from:'); -console.log(' diffMenuItem.classList.remove(\'hidden\');'); -console.log(' diffMenuItem.disabled = !hasGitChanges;'); -console.log(' to:'); -console.log(' diffMenuItem.classList.toggle(\'hidden\', !hasGitChanges);'); -console.log(''); - -console.log('\n========================================'); -console.log('SUMMARY'); -console.log('========================================\n'); - -console.log('The mobile menu logic is MOSTLY CORRECT:'); -console.log(''); -console.log('✅ Hamburger menu appears on mobile'); -console.log('✅ Preview/PDF shown only for markdown files'); -console.log('✅ Git Diff shown for files with git status (M, A, D, R)'); -console.log('⚠️ Git Diff shown but DISABLED for untracked (??) files'); -console.log(' (Could be improved by hiding instead of disabling)'); -console.log(''); -console.log('Expected behavior per file:'); -testFiles.forEach(file => { - const isMarkdown = file.name.endsWith('.md'); - const hasGitChanges = file.status !== '??'; - - const preview = isMarkdown ? '✓' : '✗'; - const pdf = isMarkdown ? '✓' : '✗'; - const diff = hasGitChanges ? '✓' : '✗ (disabled)'; - - console.log(` ${file.name.padEnd(30)} [${file.status}] → Preview:${preview} PDF:${pdf} Diff:${diff}`); -}); diff --git a/antfarm b/antfarm new file mode 160000 index 0000000..2fff211 --- /dev/null +++ b/antfarm @@ -0,0 +1 @@ +Subproject commit 2fff2115022afc796908f1a63124987437520b69 diff --git a/dashboard/api.py b/dashboard/api.py index 53818ad..15fb9f1 100644 --- a/dashboard/api.py +++ b/dashboard/api.py @@ -1759,20 +1759,33 @@ class TaskBoardHandler(SimpleHTTPRequestHandler): all_checks = all(c.get('type') == 'check' for c in recent_completions) if all_checks: habit['lives'] = min(habit['lives'] + 1, 3) - + # Update timestamp habit['updatedAt'] = datetime.now().isoformat() habits_data['lastUpdated'] = habit['updatedAt'] - + # Save to file with open(HABITS_FILE, 'w', encoding='utf-8') as f: json.dump(habits_data, f, indent=2) - - # Return updated habit - self.send_json(habit, 200) + + # Enrich habit with calculated stats before returning + current_streak = habits_helpers.calculate_streak(habit) + best_streak = habit.get('streak', {}).get('best', 0) + completion_rate = habits_helpers.get_completion_rate(habit, days=30) + weekly_summary = habits_helpers.get_weekly_summary(habit) + + enriched_habit = habit.copy() + enriched_habit['current_streak'] = current_streak + enriched_habit['best_streak'] = best_streak + enriched_habit['completion_rate_30d'] = completion_rate + enriched_habit['weekly_summary'] = weekly_summary + enriched_habit['should_check_today'] = habits_helpers.should_check_today(habit) + + # Return enriched habit + self.send_json(enriched_habit, 200) except Exception as e: self.send_json({'error': str(e)}, 500) - + def handle_habits_uncheck(self): """Uncheck a habit (remove completion for a specific date).""" try: @@ -1841,20 +1854,32 @@ class TaskBoardHandler(SimpleHTTPRequestHandler): # Update best streak if needed (best never decreases, but we keep it for consistency) if current_streak > habit['streak']['best']: habit['streak']['best'] = current_streak - + # Update timestamp habit['updatedAt'] = datetime.now().isoformat() habits_data['lastUpdated'] = habit['updatedAt'] - + # Save to file with open(HABITS_FILE, 'w', encoding='utf-8') as f: json.dump(habits_data, f, indent=2) - - # Return updated habit - self.send_json(habit, 200) + + # Enrich habit with calculated stats before returning + best_streak = habit.get('streak', {}).get('best', 0) + completion_rate = habits_helpers.get_completion_rate(habit, days=30) + weekly_summary = habits_helpers.get_weekly_summary(habit) + + enriched_habit = habit.copy() + enriched_habit['current_streak'] = current_streak + enriched_habit['best_streak'] = best_streak + enriched_habit['completion_rate_30d'] = completion_rate + enriched_habit['weekly_summary'] = weekly_summary + enriched_habit['should_check_today'] = habits_helpers.should_check_today(habit) + + # Return enriched habit + self.send_json(enriched_habit, 200) except Exception as e: self.send_json({'error': str(e)}, 500) - + def handle_habits_skip(self): """Skip a day using a life to preserve streak.""" try: @@ -1901,17 +1926,30 @@ class TaskBoardHandler(SimpleHTTPRequestHandler): 'type': 'skip' } habit['completions'].append(completion_entry) - + # Update timestamp habit['updatedAt'] = datetime.now().isoformat() habits_data['lastUpdated'] = habit['updatedAt'] - + # Save to file with open(HABITS_FILE, 'w', encoding='utf-8') as f: json.dump(habits_data, f, indent=2) - - # Return updated habit - self.send_json(habit, 200) + + # Enrich habit with calculated stats before returning + current_streak = habits_helpers.calculate_streak(habit) + best_streak = habit.get('streak', {}).get('best', 0) + completion_rate = habits_helpers.get_completion_rate(habit, days=30) + weekly_summary = habits_helpers.get_weekly_summary(habit) + + enriched_habit = habit.copy() + enriched_habit['current_streak'] = current_streak + enriched_habit['best_streak'] = best_streak + enriched_habit['completion_rate_30d'] = completion_rate + enriched_habit['weekly_summary'] = weekly_summary + enriched_habit['should_check_today'] = habits_helpers.should_check_today(habit) + + # Return enriched habit + self.send_json(enriched_habit, 200) except Exception as e: self.send_json({'error': str(e)}, 500) diff --git a/dashboard/archive/tasks-2026-02.json b/dashboard/archive/tasks-2026-02.json new file mode 100644 index 0000000..2687c0b --- /dev/null +++ b/dashboard/archive/tasks-2026-02.json @@ -0,0 +1,57 @@ +{ + "month": "2026-02", + "tasks": [ + { + "id": "task-034", + "title": "Actualizare documentație canale agenți", + "description": "", + "created": "2026-02-01T12:15:41Z", + "priority": "medium", + "completed": "2026-02-01T12:15:44Z" + }, + { + "id": "task-035", + "title": "Restructurare echipă: șterg work, unific health+growth→self", + "description": "", + "created": "2026-02-01T12:20:59Z", + "priority": "medium", + "completed": "2026-02-01T12:23:32Z" + }, + { + "id": "task-036", + "title": "Unificare în 1 agent cu tehnici diminuare dezavantaje", + "description": "", + "created": "2026-02-01T13:27:51Z", + "priority": "medium", + "completed": "2026-02-01T13:30:01Z" + }, + { + "id": "task-037", + "title": "Coaching dimineață - Asumarea eforturilor (Zoltan Vereș)", + "description": "", + "created": "2026-02-02T07:01:14Z", + "priority": "medium" + }, + { + "id": "task-038", + "title": "Raport dimineata trimis pe email", + "description": "", + "created": "2026-02-03T06:31:08Z", + "priority": "medium" + }, + { + "id": "task-039", + "title": "Raport seară 3 feb trimis pe email", + "description": "", + "created": "2026-02-03T18:01:12Z", + "priority": "medium" + }, + { + "id": "task-040", + "title": "Job night-execute: 2 video-uri YouTube procesate", + "description": "", + "created": "2026-02-03T21:02:31Z", + "priority": "medium" + } + ] +} \ No newline at end of file diff --git a/dashboard/habits.html b/dashboard/habits.html index 828deb7..d9cabe5 100644 --- a/dashboard/habits.html +++ b/dashboard/habits.html @@ -173,11 +173,11 @@ /* Compact cards stay compact on mobile */ .habit-card { min-height: 90px; - max-height: 120px; + max-height: none; } - + .habit-card-name { - font-size: var(--text-xs); + font-size: 22px; } .modal-close { @@ -190,11 +190,7 @@ grid-template-columns: repeat(2, 1fr); } - /* Icon and color pickers wrap properly */ - .color-picker-swatches { - grid-template-columns: repeat(4, 1fr); - } - + /* Icon picker wraps properly */ .icon-picker-grid { grid-template-columns: repeat(4, 1fr); max-height: 300px; @@ -225,10 +221,6 @@ } /* Larger touch targets for pickers */ - .color-swatch { - min-height: 44px; - } - .icon-option { min-height: 44px; } @@ -310,13 +302,13 @@ border: 1px solid var(--border); border-radius: var(--radius-lg); border-left: 4px solid var(--accent); - padding: var(--space-3); + padding: var(--space-4); transition: all var(--transition-base); + overflow: visible; + position: relative; display: flex; flex-direction: column; - gap: var(--space-2); - min-height: 90px; - max-height: 110px; + gap: var(--space-3); } .habit-card:hover { @@ -333,15 +325,15 @@ } .habit-card-icon { - width: 32px; - height: 32px; + width: 36px; + height: 36px; color: var(--text-primary); flex-shrink: 0; } .habit-card-name { flex: 1; - font-size: 20px; + font-size: 28px; font-weight: 600; color: var(--text-primary); white-space: nowrap; @@ -350,12 +342,30 @@ } .habit-card-streak { - font-size: 16px; + font-size: 20px; color: var(--text-muted); white-space: nowrap; flex-shrink: 0; font-weight: 600; } + + .habit-card-weekly-badge { + font-size: 14px; + color: var(--text-primary); + background: var(--accent-muted); + padding: 2px 8px; + border-radius: var(--radius-sm); + white-space: nowrap; + flex-shrink: 0; + font-weight: 600; + } + + .habit-card-lives { + font-size: 16px; + white-space: nowrap; + flex-shrink: 0; + letter-spacing: 2px; + } /* Compact check button */ .habit-card-check-btn-compact { @@ -392,35 +402,89 @@ transform: scale(1.05); } + /* Ambient Actions - Weightless until needed */ .habit-card-actions { + position: absolute; + bottom: var(--space-2); + right: var(--space-2); display: flex; gap: var(--space-1); - flex-shrink: 0; + z-index: 10; + opacity: 0; + transition: opacity 0.2s ease; } - + + .habit-card:hover .habit-card-actions { + opacity: 1; + } + + /* Mobile: always visible but subtle */ + @media (max-width: 768px) { + .habit-card-actions { + opacity: 0.4; + } + + .habit-card:active .habit-card-actions, + .habit-card-actions:focus-within { + opacity: 1; + } + } + .habit-card-action-btn { - background: none; - border: none; + background: rgba(26, 27, 30, 0.6); + backdrop-filter: blur(8px); + border: 1px solid rgba(255, 255, 255, 0.06); color: var(--text-muted); cursor: pointer; - padding: var(--space-1); + padding: var(--space-2); display: flex; align-items: center; justify-content: center; border-radius: var(--radius-md); - transition: all var(--transition-base); + transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); min-width: 44px; min-height: 44px; } - + .habit-card-action-btn:hover { - background: var(--bg-hover); + background: rgba(26, 27, 30, 0.9); + border-color: rgba(255, 255, 255, 0.12); color: var(--text-primary); + transform: scale(1.05); } - + + .habit-card-action-btn:active { + transform: scale(0.95); + } + .habit-card-action-btn svg { - width: 22px; - height: 22px; + width: 20px; + height: 20px; + } + + /* Distinctive hover colors */ + .habit-card-action-btn[data-action="skip"]:not(:disabled):hover { + background: var(--warning); + border-color: var(--warning); + color: white; + } + + .habit-card-action-btn[data-action="edit"]:hover { + background: var(--accent); + border-color: var(--accent); + color: white; + } + + .habit-card-action-btn[data-action="delete"]:hover { + background: var(--error); + border-color: var(--error); + color: white; + } + + /* Disabled state */ + .habit-card-action-btn:disabled { + opacity: 0.3; + cursor: not-allowed; } /* Progress bar row */ @@ -445,7 +509,7 @@ } .habit-card-progress-text { - font-size: 16px; + font-size: 18px; color: var(--text-muted); font-weight: 700; min-width: 40px; @@ -454,8 +518,24 @@ } /* Next date row */ + .habit-card-stats-row { + display: flex; + gap: var(--space-2); + margin-top: var(--space-2); + font-size: 14px; + flex-wrap: wrap; + } + + .habit-card-stat { + color: var(--text-primary); + background: var(--bg-muted); + padding: 4px 8px; + border-radius: var(--radius-sm); + font-weight: 600; + } + .habit-card-next-date { - font-size: 15px; + font-size: 16px; color: var(--text-muted); text-align: left; font-weight: 500; @@ -488,7 +568,7 @@ left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, 0.75) !important; + background: rgba(0, 0, 0, 0.92) !important; z-index: 1000; align-items: center; justify-content: center; @@ -500,7 +580,7 @@ } .modal { - background: var(--bg-surface); + background: #1a1b1e; border-radius: var(--radius-lg); max-width: 600px; width: 100%; @@ -508,6 +588,10 @@ overflow-y: auto; box-shadow: var(--shadow-lg); } + + [data-theme="light"] .modal { + background: #ffffff; + } .modal-header { display: flex; @@ -606,32 +690,118 @@ font-family: inherit; } - /* Color picker */ - .color-picker-swatches { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: var(--space-2); - margin-bottom: var(--space-2); - max-width: 200px; + /* Color picker dropdown */ + .color-picker-dropdown { + position: relative; } - - .color-swatch { + + .color-picker-trigger { width: 100%; - aspect-ratio: 1; - border: 2px solid transparent; + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + background: var(--bg-surface); + border: 1px solid var(--border); border-radius: var(--radius-md); cursor: pointer; transition: all var(--transition-base); - min-height: 40px; + min-height: 44px; } - - .color-swatch:hover { - transform: scale(1.05); + + .color-picker-trigger:hover { + background: var(--bg-hover); + border-color: var(--accent); } - - .color-swatch.selected { - border-color: var(--text-primary); - box-shadow: 0 0 0 2px var(--bg-surface), 0 0 0 4px var(--accent); + + .color-picker-preview { + width: 24px; + height: 24px; + border-radius: var(--radius-sm); + border: 2px solid var(--border); + flex-shrink: 0; + } + + .color-picker-name { + flex: 1; + text-align: left; + color: var(--text-primary); + font-size: var(--text-sm); + } + + .color-picker-chevron { + transition: transform var(--transition-base); + color: var(--text-muted); + } + + .color-picker-trigger.open .color-picker-chevron { + transform: rotate(180deg); + } + + .color-picker-content { + position: absolute; + top: calc(100% + var(--space-1)); + left: 0; + right: 0; + background: #1a1b1e; + border: 1px solid var(--border); + border-radius: var(--radius-md); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); + z-index: 100; + display: none; + max-height: 300px; + overflow-y: auto; + } + + .color-picker-content.visible { + display: block; + } + + .color-picker-option { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + cursor: pointer; + transition: all var(--transition-base); + min-height: 44px; + } + + .color-picker-option:hover { + background: var(--bg-hover); + } + + .color-picker-option.selected { + background: var(--accent); + color: white; + } + + .color-picker-option-swatch { + width: 24px; + height: 24px; + border-radius: var(--radius-sm); + border: 2px solid var(--border); + flex-shrink: 0; + } + + .color-picker-option-name { + flex: 1; + font-size: var(--text-sm); + } + + .color-picker-custom { + border-top: 1px solid var(--border); + padding: var(--space-2); + } + + .color-picker-custom input { + width: 100%; + padding: var(--space-2); + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--bg-base); + color: var(--text-primary); + font-size: var(--text-sm); } /* Icon picker dropdown */ @@ -683,7 +853,7 @@ top: calc(100% + var(--space-1)); left: 0; right: 0; - background: rgba(26, 27, 30, 0.98); + background: #1a1b1e; border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); @@ -730,18 +900,19 @@ border-radius: var(--radius-md); cursor: pointer; transition: all var(--transition-base); - background: var(--bg-surface); + background: transparent; min-height: 44px; } - + .icon-option:hover { - background: var(--bg-hover); + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.1); transform: scale(1.1); } - + .icon-option.selected { border-color: var(--accent); - background: var(--accent-muted); + background: rgba(59, 130, 246, 0.15); } .icon-option svg { @@ -1019,7 +1190,7 @@ .weekly-summary-content { display: none; - padding: var(--space-4); + padding: var(--space-3); } .weekly-summary-content.visible { @@ -1031,14 +1202,16 @@ align-items: flex-end; justify-content: space-between; gap: var(--space-2); - height: 150px; - margin-bottom: var(--space-4); + height: 120px; + margin-bottom: var(--space-3); } .weekly-bar-wrapper { flex: 1; + height: 100%; display: flex; flex-direction: column; + justify-content: flex-end; align-items: center; gap: var(--space-1); } @@ -1050,11 +1223,27 @@ transition: all var(--transition-base); min-height: 4px; } - + + .weekly-bar-empty { + background: var(--bg-muted); + opacity: 0.3; + border: 1px solid var(--border); + } + .weekly-bar:hover { opacity: 0.8; } - + + .weekly-bar-label { + font-size: 13px; + font-weight: 700; + color: var(--text-primary); + text-align: center; + line-height: 1.3; + margin-bottom: 6px; + white-space: nowrap; + } + .weekly-day-label { font-size: var(--text-xs); color: var(--text-muted); @@ -1185,7 +1374,15 @@
0
-
Avg Completion (30d)
+
Last 7 Days
+
0
+
+
+
Last 30 Days
+
0
+
+
+
Avg Completion
0%
@@ -1253,8 +1450,14 @@
-
- +
+ +
+
@@ -1332,7 +1535,7 @@
- +
@@ -1344,7 +1547,7 @@
- +
@@ -1364,6 +1567,25 @@
+ + +