Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
ad7dfc7
docs: archive changelog for v0.12.48 [skip ci]
github-actions[bot] Feb 14, 2026
a28fb64
build: prep v0.13.0 for next release [skip ci]
github-actions[bot] Feb 14, 2026
a60c8b1
feat(genome): add 37 cancer predisposition markers across 7 new subca…
atomantic Feb 14, 2026
1252e07
build: bump version to 0.13.1 [skip ci]
github-actions[bot] Feb 14, 2026
7786504
feat(cos): add agent task feedback system
atomantic Feb 14, 2026
246ed75
style(genome): rename Markers stat card to Found Curated Markers
atomantic Feb 14, 2026
2e4de47
build: bump version to 0.13.2 [skip ci]
github-actions[bot] Feb 14, 2026
a370fe6
refactor(cos): merge duplicate git-maintenance job into github-repo-m…
atomantic Feb 14, 2026
cae7da4
feat(cos): enable github-repo-maintenance autonomous job
atomantic Feb 14, 2026
2007461
build: bump version to 0.13.3 [skip ci]
github-actions[bot] Feb 14, 2026
8acc6a7
feat(cos): add proactive agent feedback toast notifications
atomantic Feb 15, 2026
6ac4821
feat(agents): add Moltworld platform support
atomantic Feb 15, 2026
1c99a56
docs: spec P2.5 Aesthetic Taste Prompting for Digital Twin (brain ide…
atomantic Feb 15, 2026
55859dd
fix(standardize): fix timeout race condition and increase default to …
atomantic Feb 15, 2026
5ad6a29
build: bump version to 0.13.4 [skip ci]
github-actions[bot] Feb 15, 2026
358fad3
feat(cos): add activity calendar to dashboard widget
atomantic Feb 15, 2026
1cc1214
feat(cos): add goal progress tracking widget to dashboard
atomantic Feb 16, 2026
fdf1419
feat(brain): add status tracking for ideas to prevent duplicate task …
atomantic Feb 16, 2026
3c6935a
build: bump version to 0.13.5 [skip ci]
github-actions[bot] Feb 16, 2026
21c629c
feat(cos): add goal progress tracking widget to dashboard
atomantic Feb 16, 2026
c3d05de
docs: pump.fun launch tracking data source evaluation (brain 467fbe07)
atomantic Feb 16, 2026
aca941f
fix(cos): auto-restart errored PM2 processes during health check
atomantic Feb 16, 2026
fb64e08
build: bump version to 0.13.6 [skip ci]
github-actions[bot] Feb 16, 2026
cf9ed49
fix(mobile): prevent excess space on right/bottom when zooming out
atomantic Feb 16, 2026
631df37
fix(cos): remove duplicate StateLabel on mobile, unconstrain avatar s…
atomantic Feb 16, 2026
6ef01fe
fix(cos): prevent mobile zoom-out excess space on CoS pages
atomantic Feb 16, 2026
f6ad421
fix(digital-twin): track list-based enrichment items in questionsAnsw…
atomantic Feb 16, 2026
0cf84d6
fix(cos): reduce avatar max-width to prevent clipping in sidebar
atomantic Feb 16, 2026
29cd7aa
fix(dashboard): suppress Not Found toast errors for optional dashboar…
atomantic Feb 16, 2026
f3e4e55
feat(cybercity): improve UX with better colors, readability, and inte…
atomantic Feb 16, 2026
3797c34
docs: add CyberCity UX improvements to changelog
atomantic Feb 16, 2026
20acf99
build: bump version to 0.13.7 [skip ci]
github-actions[bot] Feb 16, 2026
b1ebc33
feat(ui): add braille spinner loading indicators across the app
atomantic Feb 16, 2026
99f88ed
docs(research): add detailed API specs for pump.fun launch tracking d…
atomantic Feb 16, 2026
e5bf35f
docs(changelog): document github repo maintenance skill template impr…
atomantic Feb 16, 2026
72d552e
docs(api): add missing Browser, Genome, and Moltworld API endpoints
atomantic Feb 17, 2026
d298b9b
feat(dashboard): add Upcoming Tasks widget with schedule preview
atomantic Feb 17, 2026
75c9a38
docs(research): kalshibot health check analysis — 0% win rate, strate…
atomantic Feb 17, 2026
854ee47
feat(cos): enable resume for system agent tasks
atomantic Feb 17, 2026
190ea80
docs(research): kalshibot health check — root cause analysis and conf…
atomantic Feb 17, 2026
f559137
feat(moltworld): add World tab, WebSocket relay, action queue, explor…
atomantic Feb 17, 2026
b0a8d34
build: bump version to 0.13.8 [skip ci]
github-actions[bot] Feb 17, 2026
04235cd
fix(autofixer): inherit PATH for nvm node/pm2 access in child processes
atomantic Feb 17, 2026
b9bcc48
build: bump version to 0.13.9 [skip ci]
github-actions[bot] Feb 17, 2026
168688d
feat(pm2): support custom PM2_HOME for isolated app instances
atomantic Feb 17, 2026
f5c9f48
test(apps): update assertions for pm2Home parameter
atomantic Feb 17, 2026
188b9e1
build: bump version to 0.13.10 [skip ci]
github-actions[bot] Feb 17, 2026
33803c8
build: update portos-ai-toolkit to 0.4.2 via npm registry
atomantic Feb 17, 2026
a73b711
build: bump version to 0.13.11 [skip ci]
github-actions[bot] Feb 17, 2026
fe10bb0
address PR review: fix 9 issues from copilot review
atomantic Feb 17, 2026
8db80a3
build: bump version to 0.13.12 [skip ci]
github-actions[bot] Feb 17, 2026
94cbc65
feat: load sample providers from AI toolkit on demand
atomantic Feb 17, 2026
062dd7a
fix: add scroll arrows to CoS sub-nav for overflow tabs
atomantic Feb 17, 2026
340ef68
feat: auto-detect pm2Home from ecosystem config during ingestion
atomantic Feb 17, 2026
6308649
fix(pm2): remove PM2_HOME isolation, use global pm2 daemon
atomantic Feb 17, 2026
c464e4c
chore: simplify pm2 scripts to use global daemon
atomantic Feb 17, 2026
4947d14
build: bump version to 0.13.13 [skip ci]
github-actions[bot] Feb 17, 2026
2fb0864
feat: add environment variables editor to provider form
atomantic Feb 17, 2026
3fb55f6
feat: add claude-opus-4-6 bedrock model to sample provider
atomantic Feb 17, 2026
3ee0708
build: bump version to 0.13.14 [skip ci]
github-actions[bot] Feb 17, 2026
f7928a8
feat: add AWS_BEARER_TOKEN_BEDROCK env var to bedrock provider
atomantic Feb 17, 2026
a294f60
build: bump version to 0.13.15 [skip ci]
github-actions[bot] Feb 17, 2026
6c1c985
address review: fix eviction index bug, timer cleanup, JSON extraction
atomantic Feb 17, 2026
7be5ece
build: bump version to 0.13.16 [skip ci]
github-actions[bot] Feb 17, 2026
dbdfe58
address review: fix 8 issues — security, resilience, dead code
atomantic Feb 17, 2026
eacb0bb
build: bump version to 0.13.17 [skip ci]
github-actions[bot] Feb 17, 2026
f46f918
address review: separate PortOS vs Moltworld agent IDs
atomantic Feb 17, 2026
aaf7918
build: bump version to 0.13.18 [skip ci]
github-actions[bot] Feb 17, 2026
1c721fc
fix: properly parse template literals with nested quotes in pm2Home e…
atomantic Feb 17, 2026
540a903
build: bump version to 0.13.19 [skip ci]
github-actions[bot] Feb 17, 2026
3cc7b26
fix: add 30-minute cooldown to brain scheduler after task failures to…
atomantic Feb 17, 2026
ca2cae6
build: bump version to 0.13.20 [skip ci]
github-actions[bot] Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changelog/v0.12.x.md → .changelog/v0.12.48.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Release v0.12.x - Changelog
# Release v0.12.48 - Changelog

Released: YYYY-MM-DD

Expand Down
255 changes: 255 additions & 0 deletions .changelog/v0.13.x.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ Thumbs.db

# Browser service node_modules (code is committed)
browser/node_modules/
.pm2
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ npm run dev
cd server && npm test
cd server && npm run test:watch # Watch mode

# Production
npm run pm2:start
npm run pm2:stop
npm run pm2:logs
# Production (npm scripts or pm2 directly)
pm2 start ecosystem.config.cjs
pm2 stop ecosystem.config.cjs
pm2 logs
```

## Architecture
Expand Down
320 changes: 294 additions & 26 deletions PLAN.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "portos-client",
"version": "0.12.48",
"version": "0.13.20",
"private": true,
"type": "module",
"scripts": {
Expand Down
3 changes: 2 additions & 1 deletion client/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Suspense, lazy } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import Layout from './components/Layout';
import BrailleSpinner from './components/BrailleSpinner';
import Dashboard from './pages/Dashboard';
import Apps from './pages/Apps';
import CreateApp from './pages/CreateApp';
Expand Down Expand Up @@ -30,7 +31,7 @@ const CyberCity = lazy(() => import('./pages/CyberCity'));
// Loading fallback for lazy-loaded pages
const PageLoader = () => (
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading...</div>
<BrailleSpinner text="Loading" />
</div>
);

Expand Down
19 changes: 19 additions & 0 deletions client/src/components/BrailleSpinner.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useState, useEffect } from 'react';

const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
const INTERVAL_MS = 80;

export default function BrailleSpinner({ text, className = '' }) {
const [frame, setFrame] = useState(0);

useEffect(() => {
const id = setInterval(() => setFrame(f => (f + 1) % FRAMES.length), INTERVAL_MS);
return () => clearInterval(id);
}, []);

return (
<span className={`text-port-accent ${className}`}>
{FRAMES[frame]}{text ? ` ${text}` : ''}
</span>
);
}
128 changes: 123 additions & 5 deletions client/src/components/CosDashboardWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
Zap,
Bot,
XCircle,
History
History,
Activity
} from 'lucide-react';
import * as api from '../services/api';

Expand All @@ -23,19 +24,23 @@ const CosDashboardWidget = memo(function CosDashboardWidget() {
const [summary, setSummary] = useState(null);
const [learningSummary, setLearningSummary] = useState(null);
const [recentTasks, setRecentTasks] = useState(null);
const [activityCalendar, setActivityCalendar] = useState(null);
const [loading, setLoading] = useState(true);
const [tasksExpanded, setTasksExpanded] = useState(false);

useEffect(() => {
const loadData = async () => {
const [quickData, learningData, tasksData] = await Promise.all([
api.getCosQuickSummary().catch(() => null),
api.getCosLearningSummary().catch(() => null),
api.getCosRecentTasks(5).catch(() => null)
const silent = { silent: true };
const [quickData, learningData, tasksData, calendarData] = await Promise.all([
api.getCosQuickSummary(silent).catch(() => null),
api.getCosLearningSummary(silent).catch(() => null),
api.getCosRecentTasks(5, silent).catch(() => null),
api.getCosActivityCalendar(8, silent).catch(() => null)
]);
setSummary(quickData);
setLearningSummary(learningData);
setRecentTasks(tasksData);
setActivityCalendar(calendarData);
setLoading(false);
};

Expand Down Expand Up @@ -184,6 +189,11 @@ const CosDashboardWidget = memo(function CosDashboardWidget() {
</Link>
</div>

{/* Activity Calendar - GitHub-style heatmap */}
{activityCalendar?.weeks?.length > 0 && activityCalendar.summary.totalTasks > 0 && (
<ActivityCalendar data={activityCalendar} />
)}

{/* Recent Tasks Section */}
{recentTasks?.tasks?.length > 0 && (
<div className="mt-4 pt-4 border-t border-port-border">
Expand Down Expand Up @@ -257,4 +267,112 @@ const CosDashboardWidget = memo(function CosDashboardWidget() {
);
});

/**
* ActivityCalendar - Compact GitHub-style activity heatmap
* Shows daily task completion as colored squares
*/
function ActivityCalendar({ data }) {
// Calculate intensity level (0-4) based on tasks completed
const getIntensityLevel = (tasks) => {
if (tasks === 0) return 0;
if (tasks === 1) return 1;
const max = data.maxTasks || 1;
const ratio = tasks / max;
if (ratio >= 0.75) return 4;
if (ratio >= 0.5) return 3;
if (ratio >= 0.25) return 2;
return 1;
};

// Get color class based on intensity and success rate
const getColorClass = (day) => {
if (day.tasks === 0 || day.isFuture) return 'bg-port-border/20';
const intensity = getIntensityLevel(day.tasks);

// Color based on success rate
if (day.successRate >= 80) {
const shades = ['', 'bg-emerald-900/50', 'bg-emerald-700/60', 'bg-emerald-500/70', 'bg-emerald-400'];
return shades[intensity];
} else if (day.successRate >= 50) {
const shades = ['', 'bg-amber-900/50', 'bg-amber-700/60', 'bg-amber-500/70', 'bg-amber-400'];
return shades[intensity];
} else {
const shades = ['', 'bg-red-900/50', 'bg-red-700/60', 'bg-red-500/70', 'bg-red-400'];
return shades[intensity];
}
};

const formatDate = (dateStr) => {
const date = new Date(dateStr + 'T12:00:00');
return date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
};

return (
<div className="mt-4 pt-4 border-t border-port-border">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<Activity size={14} className="text-port-accent" />
<span className="text-sm font-medium text-gray-300">Activity</span>
{data.currentStreak > 0 && (
<span className="flex items-center gap-1 text-xs px-1.5 py-0.5 rounded bg-orange-500/20 text-orange-400">
<Flame size={10} />
{data.currentStreak}d
</span>
)}
</div>
<Link
to="/cos/productivity"
className="flex items-center gap-1 text-xs text-gray-500 hover:text-port-accent transition-colors"
>
{data.summary.activeDays} active days
<ChevronRight size={12} />
</Link>
</div>

{/* Calendar Grid */}
<div className="overflow-x-auto scrollbar-hide">
<div className="flex gap-0.5" style={{ minWidth: 'max-content' }}>
{data.weeks.map((week, weekIdx) => (
<div key={weekIdx} className="flex flex-col gap-0.5">
{week.map((day) => (
<div
key={day.date}
className={`
w-[9px] h-[9px] sm:w-[10px] sm:h-[10px]
rounded-sm transition-colors cursor-default
${getColorClass(day)}
${day.isToday ? 'ring-1 ring-port-accent' : ''}
${day.isFuture ? 'opacity-30' : ''}
`}
title={day.isFuture ? '' : `${formatDate(day.date)}: ${day.tasks} task${day.tasks !== 1 ? 's' : ''} (${day.successRate}% success)`}
/>
))}
</div>
))}
</div>
</div>

{/* Summary Row */}
<div className="flex items-center justify-between mt-2 text-xs text-gray-500">
<span>
<span className="text-white font-medium">{data.summary.totalTasks}</span> tasks,{' '}
<span className={`font-medium ${
data.summary.successRate >= 80 ? 'text-port-success' :
data.summary.successRate >= 50 ? 'text-port-warning' : 'text-port-error'
}`}>{data.summary.successRate}%</span> success
</span>
{/* Mini Legend */}
<div className="flex items-center gap-0.5">
<span className="mr-1 hidden sm:inline">Less</span>
<div className="w-2 h-2 rounded-sm bg-port-border/20" />
<div className="w-2 h-2 rounded-sm bg-emerald-900/50" />
<div className="w-2 h-2 rounded-sm bg-emerald-500/70" />
<div className="w-2 h-2 rounded-sm bg-emerald-400" />
<span className="ml-1 hidden sm:inline">More</span>
</div>
</div>
</div>
);
}

export default CosDashboardWidget;
156 changes: 156 additions & 0 deletions client/src/components/GoalProgressWidget.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { useState, useEffect, memo } from 'react';
import { Link } from 'react-router-dom';
import {
Target,
ChevronRight,
TrendingUp,
AlertTriangle
} from 'lucide-react';
import * as api from '../services/api';

/**
* GoalProgressWidget - Shows progress toward user goals on the dashboard
* Maps completed CoS tasks to goal categories from COS-GOALS.md
*/
const GoalProgressWidget = memo(function GoalProgressWidget() {
const [progress, setProgress] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const loadData = async () => {
const data = await api.getCosGoalProgressSummary({ silent: true }).catch(() => null);
setProgress(data);
setLoading(false);
};

loadData();
// Refresh every 60 seconds
const interval = setInterval(loadData, 60000);
return () => clearInterval(interval);
}, []);

// Don't render while loading or if no goals
if (loading || !progress?.goals?.length) {
return null;
}

const { goals, summary } = progress;

// Color mappings for engagement levels
const getColorClasses = (color, engagement) => {
const intensityMap = {
high: { emerald: 'bg-emerald-500', purple: 'bg-purple-500', blue: 'bg-blue-500', pink: 'bg-pink-500', green: 'bg-green-500', gray: 'bg-gray-500' },
medium: { emerald: 'bg-emerald-500/60', purple: 'bg-purple-500/60', blue: 'bg-blue-500/60', pink: 'bg-pink-500/60', green: 'bg-green-500/60', gray: 'bg-gray-500/60' },
low: { emerald: 'bg-emerald-500/30', purple: 'bg-purple-500/30', blue: 'bg-blue-500/30', pink: 'bg-pink-500/30', green: 'bg-green-500/30', gray: 'bg-gray-500/30' }
};
return intensityMap[engagement]?.[color] || 'bg-gray-500/30';
};

const getTextColorClass = (color) => {
const colorMap = {
emerald: 'text-emerald-400',
purple: 'text-purple-400',
blue: 'text-blue-400',
pink: 'text-pink-400',
green: 'text-green-400',
gray: 'text-gray-400'
};
return colorMap[color] || 'text-gray-400';
};

return (
<div className="bg-port-card border border-port-border rounded-xl p-4 sm:p-6">
{/* Header */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="text-2xl" aria-hidden="true">
<Target className="w-6 h-6 text-port-accent" />
</div>
<div>
<h3 className="text-lg font-semibold text-white">Goal Progress</h3>
<p className="text-sm text-gray-500">
{summary.totalTasks} tasks toward {summary.totalGoals} goals
</p>
</div>
</div>
<Link
to="/cos/tasks"
className="flex items-center gap-1 text-sm text-port-accent hover:text-port-accent/80 transition-colors min-h-[40px] px-2"
>
<span className="hidden sm:inline">View Tasks</span>
<ChevronRight size={16} />
</Link>
</div>

{/* Goal Progress Bars */}
<div className="space-y-3">
{goals.map((goal) => (
<div key={goal.name} className="group">
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2">
<span className="text-base" aria-hidden="true">{goal.icon}</span>
<span className="text-sm font-medium text-gray-300">{goal.name}</span>
</div>
<div className="flex items-center gap-2">
{goal.successRate !== null && (
<span className={`text-xs ${goal.successRate >= 80 ? 'text-port-success' : goal.successRate >= 50 ? 'text-port-warning' : 'text-port-error'}`}>
{goal.successRate}%
</span>
)}
<span className="text-xs text-gray-500">
{goal.tasks} task{goal.tasks !== 1 ? 's' : ''}
</span>
</div>
</div>
{/* Progress bar */}
<div className="h-2 bg-port-border rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-500 ${getColorClasses(goal.color, goal.engagement)}`}
style={{
width: `${Math.min(100, Math.max(5, (goal.tasks / Math.max(...goals.map(g => g.tasks), 1)) * 100))}%`
}}
/>
</div>
</div>
))}
</div>

{/* Insights Row */}
{(summary.mostActive || summary.leastActive) && (
<div className="mt-4 pt-4 border-t border-port-border">
<div className="flex flex-wrap gap-3 text-xs">
{summary.mostActive && (
<div className="flex items-center gap-1.5 text-port-success">
<TrendingUp size={12} />
<span>Most active: {summary.mostActive}</span>
</div>
)}
{summary.leastActive && (
<div className="flex items-center gap-1.5 text-port-warning">
<AlertTriangle size={12} />
<span>Needs attention: {summary.leastActive}</span>
</div>
)}
</div>
</div>
)}

{/* Overall Success Rate */}
{summary.overallSuccessRate !== null && (
<div className="mt-3 text-center">
<span className="text-xs text-gray-500">
Overall success rate:{' '}
<span className={`font-medium ${
summary.overallSuccessRate >= 80 ? 'text-port-success' :
summary.overallSuccessRate >= 50 ? 'text-port-warning' : 'text-port-error'
}`}>
{summary.overallSuccessRate}%
</span>
</span>
</div>
)}
</div>
);
});

export default GoalProgressWidget;
Loading