chore: auto-commit from dashboard

This commit is contained in:
2026-04-21 13:56:53 +00:00
parent a5d054d16f
commit 2dd5aee9a7
13 changed files with 2219 additions and 1545 deletions

View File

@@ -269,8 +269,8 @@
"prompt": "Heartbeat check. Rulează src/heartbeat.py printr-un scurt raport de status.\nDacă nu e nimic de raportat (email=0, calendar nu are evenimente <2h, kb ok), răspunde doar cu HEARTBEAT_OK și oprește-te — nu trimite mesaj.\nDacă e ceva: raport scurt pe Discord #echo-work.", "prompt": "Heartbeat check. Rulează src/heartbeat.py printr-un scurt raport de status.\nDacă nu e nimic de raportat (email=0, calendar nu are evenimente <2h, kb ok), răspunde doar cu HEARTBEAT_OK și oprește-te — nu trimite mesaj.\nDacă e ceva: raport scurt pe Discord #echo-work.",
"allowed_tools": [], "allowed_tools": [],
"enabled": true, "enabled": true,
"last_run": null, "last_run": "2026-04-21T13:00:00.002339+00:00",
"last_status": null, "last_status": "ok",
"next_run": null "next_run": "2026-04-21T15:00:00+00:00"
} }
] ]

View File

@@ -43,6 +43,53 @@ from handlers.pdf import PDFHandlers # noqa: E402
from handlers.workspace import WorkspaceHandlers # noqa: E402 from handlers.workspace import WorkspaceHandlers # noqa: E402
from handlers.youtube import YoutubeHandlers # noqa: E402 from handlers.youtube import YoutubeHandlers # noqa: E402
# Shared navigation injected into every served .html via <!--NAV--> marker.
NAV_HTML = '''<header class="header">
<a href="/echo/index.html" class="logo">
<i data-lucide="circle-dot"></i>
Echo
</a>
<nav class="nav">
<a href="/echo/index.html" class="nav-item" data-page="index">
<i data-lucide="layout-dashboard"></i>
<span>Dashboard</span>
</a>
<a href="/echo/workspace.html" class="nav-item" data-page="workspace">
<i data-lucide="code"></i>
<span>Workspace</span>
</a>
<a href="/echo/notes.html" class="nav-item" data-page="notes">
<i data-lucide="file-text"></i>
<span>KB</span>
</a>
<a href="/echo/habits.html" class="nav-item" data-page="habits">
<i data-lucide="dumbbell"></i>
<span>Habits</span>
</a>
<a href="/echo/files.html" class="nav-item" data-page="files">
<i data-lucide="folder"></i>
<span>Files</span>
</a>
<a href="/echo/grup-sprijin.html" class="nav-item" data-page="grup-sprijin">
<i data-lucide="heart-handshake"></i>
<span>Grup</span>
</a>
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
<i data-lucide="sun" id="themeIcon"></i>
</button>
</nav>
</header>
<script>
(function(){
var path = window.location.pathname;
var m = path.match(/([^\\/]+?)(?:\\.html)?$/);
var page = m ? m[1] : 'index';
if (!page || page === 'echo') page = 'index';
var item = document.querySelector('.nav-item[data-page="' + page + '"]');
if (item) item.classList.add('active');
})();
</script>'''
class TaskBoardHandler( class TaskBoardHandler(
GitHandlers, GitHandlers,
@@ -112,11 +159,28 @@ class TaskBoardHandler(
self.handle_eco_logs() self.handle_eco_logs()
elif self.path == '/api/eco/doctor': elif self.path == '/api/eco/doctor':
self.handle_eco_doctor() self.handle_eco_doctor()
elif self.path == '/api/eco/git' or self.path.startswith('/api/eco/git?'):
self.handle_eco_git_status()
elif self.path.startswith('/api/'): elif self.path.startswith('/api/'):
self.send_error(404) self.send_error(404)
else: else:
# Inject shared nav into served HTML pages via <!--NAV--> marker.
rel = self.path.lstrip('/').split('?')[0]
if rel.endswith('.html'):
try:
fpath = (KANBAN_DIR / rel).resolve()
fpath.relative_to(KANBAN_DIR.resolve())
except (ValueError, OSError):
self.send_error(403)
return
if fpath.is_file():
html = fpath.read_text('utf-8').replace('<!--NAV-->', NAV_HTML)
body = html.encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-cache')
self.end_headers()
self.wfile.write(body)
return
super().do_GET() super().do_GET()
def do_POST(self): def do_POST(self):

File diff suppressed because it is too large Load Diff

View File

@@ -828,41 +828,7 @@
</style> </style>
</head> </head>
<body> <body>
<header class="header"> <!--NAV-->
<a href="/echo/index.html" class="logo">
<i data-lucide="circle-dot"></i>
Echo
</a>
<nav class="nav">
<a href="/echo/index.html" class="nav-item">
<i data-lucide="layout-dashboard"></i>
<span>Dashboard</span>
</a>
<a href="/echo/workspace.html" class="nav-item">
<i data-lucide="code"></i>
<span>Workspace</span>
</a>
<a href="/echo/notes.html" class="nav-item">
<i data-lucide="file-text"></i>
<span>KB</span>
</a>
<a href="/echo/habits.html" class="nav-item">
<i data-lucide="dumbbell"></i>
<span>Habits</span>
</a>
<a href="/echo/files.html" class="nav-item active">
<i data-lucide="folder"></i>
<span>Files</span>
</a>
<a href="/echo/eco.html" class="nav-item">
<i data-lucide="cpu"></i>
<span>Eco</span>
</a>
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
<i data-lucide="sun" id="themeIcon"></i>
</button>
</nav>
</header>
<main class="main"> <main class="main">
<div class="toolbar"> <div class="toolbar">

View File

@@ -232,37 +232,7 @@
</style> </style>
</head> </head>
<body> <body>
<header class="header"> <!--NAV-->
<a href="/echo/index.html" class="logo">
<i data-lucide="circle-dot"></i>
Echo
</a>
<nav class="nav">
<a href="/echo/index.html" class="nav-item">
<i data-lucide="layout-list"></i>
<span>Tasks</span>
</a>
<a href="/echo/notes.html" class="nav-item">
<i data-lucide="file-text"></i>
<span>Notes</span>
</a>
<a href="/echo/habits.html" class="nav-item">
<i data-lucide="dumbbell"></i>
<span>Habits</span>
</a>
<a href="/echo/files.html" class="nav-item">
<i data-lucide="folder"></i>
<span>Files</span>
</a>
<a href="/echo/grup-sprijin.html" class="nav-item active">
<i data-lucide="heart-handshake"></i>
<span>Grup</span>
</a>
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
<i data-lucide="sun" id="themeIcon"></i>
</button>
</nav>
</header>
<main class="main"> <main class="main">
<div class="page-header"> <div class="page-header">

View File

@@ -1432,41 +1432,7 @@
</style> </style>
</head> </head>
<body> <body>
<header class="header"> <!--NAV-->
<a href="/echo/index.html" class="logo">
<i data-lucide="circle-dot"></i>
Echo
</a>
<nav class="nav">
<a href="/echo/index.html" class="nav-item">
<i data-lucide="layout-dashboard"></i>
<span>Dashboard</span>
</a>
<a href="/echo/workspace.html" class="nav-item">
<i data-lucide="code"></i>
<span>Workspace</span>
</a>
<a href="/echo/notes.html" class="nav-item">
<i data-lucide="file-text"></i>
<span>KB</span>
</a>
<a href="/echo/habits.html" class="nav-item active">
<i data-lucide="dumbbell"></i>
<span>Habits</span>
</a>
<a href="/echo/files.html" class="nav-item">
<i data-lucide="folder"></i>
<span>Files</span>
</a>
<a href="/echo/eco.html" class="nav-item">
<i data-lucide="cpu"></i>
<span>Eco</span>
</a>
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
<i data-lucide="sun" id="themeIcon"></i>
</button>
</nav>
</header>
<main class="main"> <main class="main">
<div class="page-header"> <div class="page-header">

View File

@@ -1,5 +1,5 @@
{ {
"lastUpdated": "2026-03-31T19:39:08.013266", "lastUpdated": "2026-04-21T13:40:29.984484",
"habits": [ "habits": [
{ {
"id": "95c15eef-3a14-4985-a61e-0b64b72851b0", "id": "95c15eef-3a14-4985-a61e-0b64b72851b0",
@@ -118,6 +118,888 @@
"createdAt": "2026-02-11T01:58:44.779904", "createdAt": "2026-02-11T01:58:44.779904",
"updatedAt": "2026-02-23T13:08:19.884995", "updatedAt": "2026-02-23T13:08:19.884995",
"lastLivesAward": "2026-02-23" "lastLivesAward": "2026-02-23"
},
{
"id": "a34a1d67-64f4-4330-bae1-ecc3abda02fb",
"name": "Morning Exercise",
"category": "health",
"color": "#10b981",
"icon": "dumbbell",
"priority": 1,
"notes": "Start with 10 push-ups",
"reminderTime": "07:00",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:39:56.225487",
"updatedAt": "2026-04-21T13:39:56.225487"
},
{
"id": "c38aa0d6-1baf-43a7-8020-aaed45eec38b",
"name": "Daily Reading",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:00.242264",
"updatedAt": "2026-04-21T13:40:00.242264"
},
{
"id": "21dc9ac0-ad31-4bd3-bdd0-eee3a05e9996",
"name": "Low Priority",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 10,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:01.101343",
"updatedAt": "2026-04-21T13:40:01.101343"
},
{
"id": "b9c48ef6-88ff-430c-95dc-b26eb41f15a9",
"name": "High Priority",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 1,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:01.102455",
"updatedAt": "2026-04-21T13:40:01.102455"
},
{
"id": "a5410380-d3fa-452c-b0c8-c2aeb3314b7a",
"name": "Medium Priority",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:01.103459",
"updatedAt": "2026-04-21T13:40:01.103459"
},
{
"id": "302ece43-2972-4541-b8d0-6451dce474bf",
"name": "Test Habit",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:02.723973",
"updatedAt": "2026-04-21T13:40:02.723973"
},
{
"id": "9ffe612d-2f84-4b75-b589-04457b93109b",
"name": "Updated Name",
"category": "productivity",
"color": "#ef4444",
"icon": "check-circle",
"priority": 1,
"notes": "New notes",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:03.528071",
"updatedAt": "2026-04-21T13:40:03.529399"
},
{
"id": "c61e531c-26e0-4223-be38-e3d77e3202d1",
"name": "Updated Name",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:04.332914",
"updatedAt": "2026-04-21T13:40:04.334508"
},
{
"id": "71388af9-fcab-4910-a5d6-74a27cc4676d",
"name": "Test Habit",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:05.941683",
"updatedAt": "2026-04-21T13:40:05.941683"
},
{
"id": "edb52e78-e6e0-4a03-973f-0839690bb8ae",
"name": "Habit to Delete",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:06.749371",
"updatedAt": "2026-04-21T13:40:06.749371"
},
{
"id": "595ce224-d2b9-4ce5-b334-2fec32d388ad",
"name": "Morning Exercise",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 1,
"best": 1,
"lastCheckIn": "2026-04-21"
},
"lives": 3,
"completions": [
{
"date": "2026-04-21",
"type": "check"
}
],
"createdAt": "2026-04-21T13:40:09.177939",
"updatedAt": "2026-04-21T13:40:09.180079"
},
{
"id": "62c9952e-6f6b-47ac-91e3-22cf277767fe",
"name": "Meditation",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 1,
"best": 1,
"lastCheckIn": "2026-04-21"
},
"lives": 3,
"completions": [
{
"date": "2026-04-21",
"type": "check",
"note": "Felt very relaxed today",
"rating": 5,
"mood": "happy"
}
],
"createdAt": "2026-04-21T13:40:09.983840",
"updatedAt": "2026-04-21T13:40:09.985357"
},
{
"id": "f3065043-265b-404f-916e-f662f3d17c66",
"name": "Monday Only Habit",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "specific_days",
"days": [
2
]
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:11.592610",
"updatedAt": "2026-04-21T13:40:11.592610"
},
{
"id": "b97c524e-c243-48da-9d47-4646206806d1",
"name": "Water Plants",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 1,
"best": 1,
"lastCheckIn": "2026-04-21"
},
"lives": 3,
"completions": [
{
"date": "2026-04-21",
"type": "check"
}
],
"createdAt": "2026-04-21T13:40:12.398205",
"updatedAt": "2026-04-21T13:40:12.399838"
},
{
"id": "21e3c623-3370-4ccc-84a8-e468277ed93a",
"name": "Read",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 1,
"best": 1,
"lastCheckIn": "2026-04-21"
},
"lives": 3,
"completions": [
{
"date": "2026-04-21",
"type": "check"
}
],
"createdAt": "2026-04-21T13:40:13.204268",
"updatedAt": "2026-04-21T13:40:13.205755"
},
{
"id": "c71332bd-c0f2-4e79-bb29-97bb513cc5f1",
"name": "Floss",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 1,
"best": 1,
"lastCheckIn": "2026-04-21"
},
"lives": 3,
"completions": [
{
"date": "2026-04-21",
"type": "check"
}
],
"createdAt": "2026-04-21T13:40:14.009917",
"updatedAt": "2026-04-21T13:40:14.011571"
},
{
"id": "4057bfad-6754-493b-be59-8e6d00a9426b",
"name": "Yoga",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 1,
"best": 1,
"lastCheckIn": "2026-04-21"
},
"lives": 3,
"completions": [
{
"date": "2026-04-21",
"type": "check"
}
],
"createdAt": "2026-04-21T13:40:14.814725",
"updatedAt": "2026-04-21T13:40:14.816582"
},
{
"id": "135607ac-44c9-4dc5-b8b6-dcb958680e3a",
"name": "Journal",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:15.620420",
"updatedAt": "2026-04-21T13:40:15.620420"
},
{
"id": "6166cffa-0bf6-4088-a552-ce8961a6f3d3",
"name": "Gratitude",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:16.425718",
"updatedAt": "2026-04-21T13:40:16.425718"
},
{
"id": "f865fb79-e1a9-4eb4-a36d-f1b048db5095",
"name": "Daily Exercise",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 2,
"completions": [
{
"date": "2026-04-21",
"type": "skip"
}
],
"createdAt": "2026-04-21T13:40:17.230280",
"updatedAt": "2026-04-21T13:40:17.231880"
},
{
"id": "6926b37a-4b2a-4334-b382-bbacd2f3fa38",
"name": "Daily Exercise",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 1,
"best": 1,
"lastCheckIn": "2026-04-21"
},
"lives": 3,
"completions": [
{
"date": "2026-04-21",
"type": "check"
}
],
"createdAt": "2026-04-21T13:40:18.035252",
"updatedAt": "2026-04-21T13:40:18.036876"
},
{
"id": "13ca7da8-f9f9-4d04-abe1-72d9e018f729",
"name": "Daily Exercise",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 0,
"completions": [
{
"date": "2026-04-21",
"type": "skip"
},
{
"date": "2026-04-21",
"type": "skip"
},
{
"date": "2026-04-21",
"type": "skip"
}
],
"createdAt": "2026-04-21T13:40:19.658427",
"updatedAt": "2026-04-21T13:40:19.663718"
},
{
"id": "100b4b01-10ab-435a-a269-4413fb86e7c8",
"name": "Daily Exercise",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 2,
"completions": [
{
"date": "2026-04-21",
"type": "skip"
}
],
"createdAt": "2026-04-21T13:40:20.469061",
"updatedAt": "2026-04-21T13:40:20.470855"
},
{
"id": "cf0cb657-f384-4a0d-a3a5-83bff1a1acaa",
"name": "Daily Exercise",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 1,
"lastCheckIn": "2026-04-21"
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:21.274674",
"updatedAt": "2026-04-21T13:40:21.278631"
},
{
"id": "1e5ac598-0984-4d5e-b5ff-59d29e95ce7b",
"name": "Daily Exercise",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:22.082884",
"updatedAt": "2026-04-21T13:40:22.082884"
},
{
"id": "4ca5ce94-52d8-44ab-9a52-07c4ba736d00",
"name": "Daily Exercise",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:23.691843",
"updatedAt": "2026-04-21T13:40:23.691843"
},
{
"id": "de01102c-e58a-4215-aee3-73211cbca676",
"name": "Daily Exercise",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 1,
"lastCheckIn": "2026-04-21"
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:24.514309",
"updatedAt": "2026-04-21T13:40:24.518328"
},
{
"id": "6dc26d9f-9631-447e-8669-a36a7b41656a",
"name": "Daily Exercise",
"category": "other",
"color": "#3b82f6",
"icon": "check-circle",
"priority": 5,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:25.322498",
"updatedAt": "2026-04-21T13:40:25.322498"
},
{
"id": "58b22147-ea36-4219-ac8a-488df8d7dc0a",
"name": "Morning meditation",
"category": "health",
"color": "#10B981",
"icon": "brain",
"priority": 50,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 1,
"best": 1,
"lastCheckIn": "2026-04-21"
},
"lives": 3,
"completions": [
{
"date": "2026-04-21",
"type": "check"
}
],
"createdAt": "2026-04-21T13:40:26.420091",
"updatedAt": "2026-04-21T13:40:26.421942"
},
{
"id": "00b8e0d2-f146-4446-89c1-42b5efe9b2c6",
"name": "Daily exercise",
"category": "health",
"color": "#EF4444",
"icon": "dumbbell",
"priority": 50,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:26.925543",
"updatedAt": "2026-04-21T13:40:26.925543"
},
{
"id": "3f97d521-c2eb-4d7d-a5b9-97b99e21fb5a",
"name": "Read book",
"category": "growth",
"color": "#3B82F6",
"icon": "book",
"priority": 50,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:27.435565",
"updatedAt": "2026-04-21T13:40:27.435565"
},
{
"id": "24fc969a-6b4c-42ab-822a-d3183d0fc91b",
"name": "Yoga practice",
"category": "health",
"color": "#8B5CF6",
"icon": "heart",
"priority": 50,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 0,
"completions": [
{
"date": "2026-04-21",
"type": "skip"
},
{
"date": "2026-04-21",
"type": "skip"
},
{
"date": "2026-04-21",
"type": "skip"
}
],
"createdAt": "2026-04-21T13:40:27.944779",
"updatedAt": "2026-04-21T13:40:27.949587"
},
{
"id": "a3e13785-08c2-4866-a764-45451e8dfbba",
"name": "Code review",
"category": "work",
"color": "#F59E0B",
"icon": "code",
"priority": 50,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "specific_days",
"days": [
"monday",
"wednesday"
]
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:28.453967",
"updatedAt": "2026-04-21T13:40:28.457179"
},
{
"id": "18eddd3f-c4f0-4023-ac68-6e3cd33ca04a",
"name": "Guitar practice",
"category": "personal",
"color": "#EC4899",
"icon": "music",
"priority": 50,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:28.962515",
"updatedAt": "2026-04-21T13:40:28.962515"
},
{
"id": "8509c820-c187-4d7d-9f01-d754c7ff6bc3",
"name": "Gym workout",
"category": "health",
"color": "#EF4444",
"icon": "dumbbell",
"priority": 50,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "specific_days",
"days": [
"monday",
"wednesday"
]
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:29.478868",
"updatedAt": "2026-04-21T13:40:29.478868"
},
{
"id": "e0961521-81f7-471f-b794-c42d2650a95b",
"name": "Meditation",
"category": "health",
"color": "#10B981",
"icon": "brain",
"priority": 50,
"notes": "",
"reminderTime": "",
"frequency": {
"type": "daily"
},
"streak": {
"current": 0,
"best": 0,
"lastCheckIn": null
},
"lives": 3,
"completions": [],
"createdAt": "2026-04-21T13:40:29.984484",
"updatedAt": "2026-04-21T13:40:29.984484"
} }
] ]
} }

View File

@@ -102,41 +102,6 @@ class GitHandlers:
except Exception as e: except Exception as e:
self.send_json({'error': str(e)}, 500) self.send_json({'error': str(e)}, 500)
# ── /api/eco/git (echo-core repo) ────────────────────────────
def handle_eco_git_status(self):
"""Get git status for echo-core repo."""
try:
workspace = constants.ECHO_CORE_DIR
branch = self._run_git(workspace, ['branch', '--show-current']).stdout.strip()
last_commit = self._run_git(workspace, ['log', '-1', '--format=%h|%s|%cr']).stdout.strip()
commit_parts = last_commit.split('|') if last_commit else ['', '', '']
status_output = self._run_git(workspace, ['status', '--short']).stdout.strip()
uncommitted = [f for f in status_output.split('\n') if f.strip()] if status_output else []
uncommitted_parsed = []
for line in uncommitted:
if len(line) >= 2:
status = line[:2].strip()
filepath = line[2:].strip()
if filepath:
uncommitted_parsed.append({'status': status, 'path': filepath})
self.send_json({
'branch': branch,
'clean': len(uncommitted) == 0,
'uncommittedCount': len(uncommitted),
'uncommittedParsed': uncommitted_parsed,
'lastCommit': {
'hash': commit_parts[0] if len(commit_parts) > 0 else '',
'message': commit_parts[1] if len(commit_parts) > 1 else '',
'time': commit_parts[2] if len(commit_parts) > 2 else '',
},
})
except Exception as e:
self.send_json({'error': str(e)}, 500)
def handle_eco_git_commit(self): def handle_eco_git_commit(self):
"""Run git add, commit, and push for echo-core repo.""" """Run git add, commit, and push for echo-core repo."""
try: try:

File diff suppressed because it is too large Load Diff

View File

@@ -678,41 +678,7 @@
</style> </style>
</head> </head>
<body> <body>
<header class="header"> <!--NAV-->
<a href="/echo/index.html" class="logo">
<i data-lucide="circle-dot"></i>
Echo
</a>
<nav class="nav">
<a href="/echo/index.html" class="nav-item">
<i data-lucide="layout-dashboard"></i>
<span>Dashboard</span>
</a>
<a href="/echo/workspace.html" class="nav-item">
<i data-lucide="code"></i>
<span>Workspace</span>
</a>
<a href="/echo/notes.html" class="nav-item active">
<i data-lucide="file-text"></i>
<span>KB</span>
</a>
<a href="/echo/habits.html" class="nav-item">
<i data-lucide="dumbbell"></i>
<span>Habits</span>
</a>
<a href="/echo/files.html" class="nav-item">
<i data-lucide="folder"></i>
<span>Files</span>
</a>
<a href="/echo/eco.html" class="nav-item">
<i data-lucide="cpu"></i>
<span>Eco</span>
</a>
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
<i data-lucide="sun" id="themeIcon"></i>
</button>
</nav>
</header>
<main class="main"> <main class="main">
<div class="page-header"> <div class="page-header">

View File

@@ -3,7 +3,7 @@
* Swipe left/right to navigate between pages * Swipe left/right to navigate between pages
*/ */
(function() { (function() {
const pages = ['index.html', 'eco.html', 'notes.html', 'habits.html', 'files.html', 'workspace.html']; const pages = ['index.html', 'notes.html', 'habits.html', 'files.html', 'workspace.html'];
// Get current page index // Get current page index
function getCurrentIndex() { function getCurrentIndex() {

View File

@@ -422,41 +422,7 @@
</style> </style>
</head> </head>
<body> <body>
<header class="header"> <!--NAV-->
<a href="/echo/index.html" class="logo">
<i data-lucide="circle-dot"></i>
Echo
</a>
<nav class="nav">
<a href="/echo/index.html" class="nav-item">
<i data-lucide="layout-dashboard"></i>
<span>Dashboard</span>
</a>
<a href="/echo/workspace.html" class="nav-item active">
<i data-lucide="code"></i>
<span>Workspace</span>
</a>
<a href="/echo/notes.html" class="nav-item">
<i data-lucide="file-text"></i>
<span>KB</span>
</a>
<a href="/echo/habits.html" class="nav-item">
<i data-lucide="dumbbell"></i>
<span>Habits</span>
</a>
<a href="/echo/files.html" class="nav-item">
<i data-lucide="folder"></i>
<span>Files</span>
</a>
<a href="/echo/eco.html" class="nav-item">
<i data-lucide="cpu"></i>
<span>Eco</span>
</a>
<button class="theme-toggle" onclick="toggleTheme()" title="Schimba tema">
<i data-lucide="sun" id="themeIcon"></i>
</button>
</nav>
</header>
<main class="main"> <main class="main">
<div class="page-header"> <div class="page-header">

View File

@@ -0,0 +1,88 @@
"""Smoke tests for the unified Echo dashboard (post index+eco merge)."""
import json
from pathlib import Path
import pytest
import requests
BASE = 'http://localhost:8088'
DASH = Path(__file__).resolve().parent.parent / 'dashboard'
SIBLING_PAGES = ['index', 'habits', 'files', 'workspace', 'notes', 'grup-sprijin']
def _taskboard_running():
try:
return requests.get(f'{BASE}/api/status', timeout=2).ok
except Exception:
return False
pytestmark = pytest.mark.skipif(
not _taskboard_running(),
reason='echo-taskboard not reachable on :8088 — start it with `systemctl --user start echo-taskboard`',
)
@pytest.mark.parametrize('page', SIBLING_PAGES)
def test_no_eco_link_on_sibling_pages(page):
"""No sibling page should still have a clickable link to the removed eco.html."""
r = requests.get(f'{BASE}/{page}.html', timeout=5)
assert r.status_code == 200, f'{page}.html returned {r.status_code}'
# Only reject actual hyperlinks, not historical code comments.
assert 'href="/echo/eco.html"' not in r.text, f'{page}.html still links to eco.html'
assert "href='/echo/eco.html'" not in r.text
def test_index_has_all_panels():
"""Unified index must render Git, Services, Sessions, Logs, Doctor panels."""
r = requests.get(f'{BASE}/index.html', timeout=5)
assert r.status_code == 200
for sid in ['sec-git', 'sec-services', 'sec-sessions', 'sec-logs', 'sec-doctor']:
assert f'id="{sid}"' in r.text, f'missing panel #{sid}'
def test_swipe_nav_no_eco():
"""swipe-nav.js hardcoded page list must no longer include eco.html."""
js = (DASH / 'swipe-nav.js').read_text(encoding='utf-8')
assert "'eco.html'" not in js
def test_eco_html_gone():
"""The old eco.html route should now 404."""
r = requests.get(f'{BASE}/eco.html', timeout=5)
assert r.status_code == 404
def test_api_git_has_diffstat_field():
"""Regression guard: the Git panel depends on /api/git returning diffStat."""
r = requests.get(f'{BASE}/api/git', timeout=5)
assert r.status_code == 200
data = r.json()
assert 'diffStat' in data
assert 'uncommittedParsed' in data
assert 'lastCommit' in data
def test_api_eco_git_removed():
"""The orphaned /api/eco/git GET handler was deleted during unification."""
r = requests.get(f'{BASE}/api/eco/git', timeout=5)
assert r.status_code == 404
def test_nav_injected_server_side():
"""Server must replace <!--NAV--> with real nav markup on every .html."""
r = requests.get(f'{BASE}/index.html', timeout=5)
assert r.status_code == 200
assert '<!--NAV-->' not in r.text, 'server did not expand the NAV marker'
assert 'class="nav-item"' in r.text, 'nav did not render'
assert 'data-page="index"' in r.text
@pytest.mark.parametrize('page', SIBLING_PAGES)
def test_nav_present_on_all_pages(page):
"""Every sibling page must receive the injected nav."""
r = requests.get(f'{BASE}/{page}.html', timeout=5)
assert r.status_code == 200
assert 'class="nav-item"' in r.text
assert '<!--NAV-->' not in r.text