Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4119a34
chore(deps): bump actions/setup-dotnet from 5.2.0 to 5.3.0
dependabot[bot] Jun 2, 2026
2af6dd8
chore(deps): bump the npm-patch-minor group across 1 directory with 5…
dependabot[bot] Jun 2, 2026
c34d07e
feat(frontend): quick-save (autosave) for task-branch and category ed…
claude Jun 2, 2026
43a8e98
feat(todo): subtasks backend — tree-structured child tasks in the Tod…
claude Jun 2, 2026
a0fc995
feat(frontend): subtasks UI in the task branch + dashboard stat wiring
claude Jun 2, 2026
5a632ce
feat(subtasks): global completion + owner-only inline editing
claude Jun 2, 2026
3fc5acd
feat(subtasks): branch system messages on create/complete + non-bold …
claude Jun 2, 2026
02d58d5
refactor(subtasks): inline branch cards authored like the description
4Keyy Jun 2, 2026
41fcb18
feat(subtasks): task-like cards, branch delete cascade, composer auto…
4Keyy Jun 2, 2026
863c43b
feat(branch): drop modal footer, wrap subtask titles, empty "+" when …
4Keyy Jun 2, 2026
fb6b50d
feat(subtasks): 1500-char titles; fix tasks filter vanishing after cr…
4Keyy Jun 3, 2026
0a569fe
fix(subtasks): schema-qualify Title column widening (fixes 500 on lon…
4Keyy Jun 3, 2026
b9297ae
feat(subtasks): fold lifecycle events into an integrated card cluster
4Keyy Jun 3, 2026
dcd1d21
feat(subtasks): branch the subtask cluster off the rail
4Keyy Jun 3, 2026
3a02b89
feat(subtasks): drop creation notice; icon-less completion reply on a…
4Keyy Jun 3, 2026
39cbee8
feat(subtasks): global take-into-work; all viewers see an In-progress…
4Keyy Jun 3, 2026
7c4267a
feat(subtasks): per-user in-work with a worker count; hide all subtas…
4Keyy Jun 3, 2026
fbc3fb6
fix(dashboard): narrow weekly stats fetch
vichca Jun 3, 2026
a9112b2
merge(fixes): subtasks, autosave & dashboard weekly-stats narrowing (…
claude Jun 3, 2026
1194627
merge(deps): bump actions/setup-dotnet 5.2.0 -> 5.3.0 (PR #85)
claude Jun 3, 2026
28229dd
merge(deps): bump npm-patch-minor group in /frontend, 5 updates (PR #89)
claude Jun 3, 2026
5aa2998
chore(deps): bump the npm-patch-minor group across 1 directory with 3…
dependabot[bot] Jun 3, 2026
34a7c0e
merge(deps): bump npm-patch-minor group in /frontend, 3 updates (PR #93)
claude Jun 3, 2026
23d8cf4
fix(subtasks): make in-work status per-viewer, not global
claude Jun 3, 2026
b4a9607
ci(e2e): generate JWT/gRPC secrets as hex, not multi-line base64
claude Jun 3, 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
4 changes: 2 additions & 2 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ jobs:
echo "REDIS_PASSWORD=$(openssl rand -hex 24)"
echo "RABBITMQ_USER=planora_e2e"
echo "RABBITMQ_PASSWORD=$(openssl rand -hex 24)"
echo "JWT_SECRET=$(openssl rand -base64 64)"
echo "GRPC_SERVICE_KEY=$(openssl rand -base64 32)"
echo "JWT_SECRET=$(openssl rand -hex 64)"
echo "GRPC_SERVICE_KEY=$(openssl rand -hex 32)"
echo "NEXT_PUBLIC_API_URL=http://127.0.0.1:5132"
} > .env.e2e

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/perf-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ jobs:
echo "REDIS_PASSWORD=$(openssl rand -hex 24)"
echo "RABBITMQ_USER=planora_perf"
echo "RABBITMQ_PASSWORD=$(openssl rand -hex 24)"
echo "JWT_SECRET=$(openssl rand -base64 64)"
echo "GRPC_SERVICE_KEY=$(openssl rand -base64 32)"
echo "JWT_SECRET=$(openssl rand -hex 64)"
echo "GRPC_SERVICE_KEY=$(openssl rand -hex 32)"
echo "NEXT_PUBLIC_API_URL=http://127.0.0.1:5132"
} > .env.perf

Expand Down
42 changes: 21 additions & 21 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "^1.2.4",
"@tanstack/react-query": "^5.100.14",
"@tanstack/react-query": "^5.101.0",
"@types/three": "^0.184.1",
"axios": "^1.16.1",
"axios": "^1.17.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.3.0",
Expand All @@ -54,7 +54,7 @@
},
"devDependencies": {
"@playwright/test": "^1.57.0",
"@tanstack/react-query-devtools": "^5.100.14",
"@tanstack/react-query-devtools": "^5.101.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
Expand Down
54 changes: 36 additions & 18 deletions frontend/src/components/todos/edit-todo-modal/branch-feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1464,10 +1464,15 @@ function SubtaskCard({

const completedName = meta.completedBy?.trim()
const completedAt = meta.completedAt
// Someone (anyone) is working on it → amber accents; the viewer's own membership drives the toggle.
// "In work" is strictly per-user. The viewer's OWN membership (`meWorking`) is the only thing that
// drives the card's amber "in progress" treatment — so taking a subtask into work changes how the
// card looks ONLY for the person who did it; its status never flips for anyone else. Other people
// working on it are surfaced through an unobtrusive presence badge (below), never by restyling the
// card. `someoneWorking` therefore gates only that badge, not the status visuals.
const someoneWorking = workerCount > 0
// Sub-branch accent — the little branch the subtask hangs from, tinted to its state.
const branchColor = done ? "#a7f3d0" : someoneWorking ? "#fcd98c" : "#e1e1e6"
const meWorking = viewerWorking
// Sub-branch accent — tinted amber only when THIS viewer is working; neutral for everyone else.
const branchColor = done ? "#a7f3d0" : meWorking ? "#fcd98c" : "#e1e1e6"

return (
<motion.div
Expand Down Expand Up @@ -1514,7 +1519,7 @@ function SubtaskCard({
left: SUB_TOGGLE_X - SUBTASK_TOGGLE / 2,
top: "50%",
width: SUBTASK_TOGGLE, height: SUBTASK_TOGGLE, borderRadius: "50%",
border: done ? "none" : `2px solid ${someoneWorking ? "#f59e0b" : "#d4d4d4"}`,
border: done ? "none" : `2px solid ${meWorking ? "#f59e0b" : "#d4d4d4"}`,
background: done ? "#10b981" : "#ffffff",
boxShadow: done
? "0 0 0 3px #ffffff, 0 2px 6px -1px rgba(16,185,129,0.5)"
Expand All @@ -1530,7 +1535,7 @@ function SubtaskCard({
<motion.span key="done" initial={{ scale: 0, rotate: -30 }} animate={{ scale: 1, rotate: 0 }} exit={{ scale: 0 }} transition={{ type: "spring", stiffness: 500, damping: 18 }}>
<Check size={15} color="white" strokeWidth={3} />
</motion.span>
) : someoneWorking ? (
) : meWorking ? (
<motion.span key="work" initial={{ scale: 0 }} animate={{ scale: 1 }} style={{ display: "flex" }}>
<span style={{ width: 8, height: 8, borderRadius: "50%", background: "#f59e0b" }} className="animate-pulse" />
</motion.span>
Expand All @@ -1549,8 +1554,8 @@ function SubtaskCard({
display: "flex", alignItems: "flex-start", gap: 10,
padding: "11px 12px", paddingRight: bodyPaddingRight,
borderRadius: 12,
background: done ? "#f7fdfb" : someoneWorking ? "#fffdf5" : "#fafafa",
border: `1px solid ${done ? "#d7f5ea" : someoneWorking && !done ? "#fde68a" : "#f0f0f0"}`,
background: done ? "#f7fdfb" : meWorking ? "#fffdf5" : "#fafafa",
border: `1px solid ${done ? "#d7f5ea" : meWorking ? "#fde68a" : "#f0f0f0"}`,
transition: "background 200ms, border-color 200ms, padding 160ms",
}}>
{/* Title (wraps freely) or inline editor */}
Expand Down Expand Up @@ -1597,32 +1602,45 @@ function SubtaskCard({
</span>
)}

{/* In-work presence badge — per-user, shown to EVERY viewer as an anonymous count
("N working"). It never names anyone. When the viewer is one of the workers the badge
reads "You + N" so they can tell their own state at a glance. */}
{/* Presence badge — the ONLY signal other people get that someone is on this subtask, since
their card stays status-neutral. Two variants:
• the viewer themselves (`meWorking`) → an amber "You're working" / "You + N" chip that
matches their own in-work card treatment;
• everyone else → a muted, status-neutral "N working" pill that simply announces the
presence without making the subtask look in-progress for them. */}
<AnimatePresence initial={false}>
{someoneWorking && !done && !editing && (
<motion.span
initial={{ opacity: 0, scale: 0.7 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.7 }}
transition={{ type: "spring", stiffness: 480, damping: 26 }}
title={`${workerCount} ${workerCount === 1 ? "person is" : "people are"} working on this`}
title={
meWorking
? (workerCount > 1
? `You and ${workerCount - 1} other ${workerCount - 1 === 1 ? "person are" : "people are"} working on this`
: "You're working on this")
: `${workerCount} ${workerCount === 1 ? "person is" : "people are"} working on this`
}
style={{
display: "inline-flex", alignItems: "center", gap: 6, flexShrink: 0, marginTop: 2,
fontSize: 9.5, fontWeight: 900, letterSpacing: "0.06em", textTransform: "uppercase",
color: "#b45309",
background: "linear-gradient(180deg,#fff8e6,#fef0c7)",
border: "1px solid #fce4a6",
color: meWorking ? "#b45309" : "#64748b",
background: meWorking
? "linear-gradient(180deg,#fff8e6,#fef0c7)"
: "linear-gradient(180deg,#f8fafc,#eef2f6)",
border: `1px solid ${meWorking ? "#fce4a6" : "#e2e8f0"}`,
padding: "3px 9px 3px 7px", borderRadius: 999,
boxShadow: "0 1px 3px -1px rgba(245,158,11,0.35)",
boxShadow: meWorking
? "0 1px 3px -1px rgba(245,158,11,0.35)"
: "0 1px 2px -1px rgba(100,116,139,0.25)",
}}
>
<span style={{ position: "relative", width: 7, height: 7, flexShrink: 0 }}>
<span className="animate-ping" style={{ position: "absolute", inset: 0, borderRadius: "50%", background: "#f59e0b", opacity: 0.6 }} />
<span style={{ position: "absolute", inset: 1, borderRadius: "50%", background: "#f59e0b" }} />
<span className="animate-ping" style={{ position: "absolute", inset: 0, borderRadius: "50%", background: meWorking ? "#f59e0b" : "#94a3b8", opacity: 0.6 }} />
<span style={{ position: "absolute", inset: 1, borderRadius: "50%", background: meWorking ? "#f59e0b" : "#94a3b8" }} />
</span>
{viewerWorking
{meWorking
? (workerCount > 1 ? `You + ${workerCount - 1} working` : "You're working")
: `${workerCount} working`}
</motion.span>
Expand Down
Loading