diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 55739743..78a9af9a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -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 diff --git a/.github/workflows/perf-smoke.yml b/.github/workflows/perf-smoke.yml index 332660d2..a66701f3 100644 --- a/.github/workflows/perf-smoke.yml +++ b/.github/workflows/perf-smoke.yml @@ -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 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 86498008..58957af4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,9 +21,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", @@ -45,7 +45,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", @@ -5929,9 +5929,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.100.14", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.14.tgz", - "integrity": "sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==", + "version": "5.101.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.0.tgz", + "integrity": "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==", "license": "MIT", "funding": { "type": "github", @@ -5939,9 +5939,9 @@ } }, "node_modules/@tanstack/query-devtools": { - "version": "5.100.14", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.100.14.tgz", - "integrity": "sha512-g96SmSSQecYTYcyuAMRXr895GplJv01UGt7qttQWPOUyZ5EGz5tbRc589bMc2m5BsPFD6O0PCEAHdbDYNP6UBw==", + "version": "5.101.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.101.0.tgz", + "integrity": "sha512-MVqw17k08RQtGGLEL654+dX/btbX9p/8WjkznO//zusLTMaObxi3Q+MoFwGVkC9K3tqjn8qrrNhJevXx4fJTeQ==", "dev": true, "license": "MIT", "funding": { @@ -5950,12 +5950,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.100.14", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.14.tgz", - "integrity": "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==", + "version": "5.101.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.0.tgz", + "integrity": "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.100.14" + "@tanstack/query-core": "5.101.0" }, "funding": { "type": "github", @@ -5966,20 +5966,20 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.100.14", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.100.14.tgz", - "integrity": "sha512-JkP5VDgKOw3t/QSA1OABRHEqx8BuNs5MfvZRooNqdvN57SzTuGq3fKR1a2IH5rqa5HDLUm+FOXUEnB9ueHiLzg==", + "version": "5.101.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.101.0.tgz", + "integrity": "sha512-cpZA0+WqKXwrwMfiWZEGGF6QrIWVQFbhBtxqDF5sQsAfrFf47HIE6fiPbQU3wyAUEN2+7UNqLCQe7oG6m3f93w==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/query-devtools": "5.100.14" + "@tanstack/query-devtools": "5.101.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.100.14", + "@tanstack/react-query": "^5.101.0", "react": "^18 || ^19" } }, @@ -7524,9 +7524,9 @@ } }, "node_modules/axios": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", - "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", + "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", diff --git a/frontend/package.json b/frontend/package.json index 704018d8..056d9dd1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", @@ -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", diff --git a/frontend/src/components/todos/edit-todo-modal/branch-feed.tsx b/frontend/src/components/todos/edit-todo-modal/branch-feed.tsx index 62f685c6..4423c0d9 100644 --- a/frontend/src/components/todos/edit-todo-modal/branch-feed.tsx +++ b/frontend/src/components/todos/edit-todo-modal/branch-feed.tsx @@ -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 ( - ) : someoneWorking ? ( + ) : meWorking ? ( @@ -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 */} @@ -1597,9 +1602,12 @@ function SubtaskCard({ )} - {/* 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. */} {someoneWorking && !done && !editing && ( 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)", }} > - - + + - {viewerWorking + {meWorking ? (workerCount > 1 ? `You + ${workerCount - 1} working` : "You're working") : `${workerCount} working`}