Skip to content

feat(projects): members canvas checkboxes + exclusive Lead (#1800 slice 6)#1828

Closed
jaylfc wants to merge 1 commit into
feat/lead-identity-basefrom
feat/lead-identity-members-ui
Closed

feat(projects): members canvas checkboxes + exclusive Lead (#1800 slice 6)#1828
jaylfc wants to merge 1 commit into
feat/lead-identity-basefrom
feat/lead-identity-members-ui

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 14, 2026

Copy link
Copy Markdown
Owner

DO NOT MERGE


Summary by Gitar

  • Refactored Project Lead designation:
    • Replaced the per-member is_lead flag with a single, project-level lead_member_id pointer (D7) to enforce an exclusive "one lead per project" invariant.
    • Added database backfilling to migrate existing is_lead flags to the new lead_member_id column.
  • Updated Lead Management API:
    • Migrated the lead promotion route from PATCH /api/projects/{pid}/members/{mid}/lead to PATCH /api/projects/{pid}/lead.
    • Added automatic activity logging for project lead changes.
  • Enhanced Canvas permissions UI:
    • Split canvas access into separate can_read_canvas and can_edit_canvas checkboxes for finer granularity.
    • Updated canvasApi.setPermission to handle specific capability flags instead of a single binary toggle.
  • Project Members UI:
    • Added a new select element in ProjectMembers to manage the exclusive project lead directly.

This will update automatically on new commits.

…ce 6)

Implement slice 6 of the lead-agent identity and canvas access epic.

Backend:
- projects gains lead_member_id; backfill it from the single is_lead flag
  and retire set_member_lead.
- Add PATCH /api/projects/{project_id}/lead to set or clear the exclusive
  lead; remove the old per-member lead route.
- The a2a quiet-filter and settings.leads sync read lead_member_id.

Frontend:
- ProjectMembers gains a second Can read canvas checkbox and an exclusive
  Lead selector driven by lead_member_id.
- projects.setLead is now project-level and canvas-api.setPermission takes a
  read vs edit flag.

Tests cover exclusive lead promotion and clearing, 404 on non-member,
session-only gating, a2a quiet-filter sync, and the selector/read checkbox.

Docs-Reviewed: members canvas checkboxes + exclusive lead per lead-agent-identity-and-canvas-access.md
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • master
  • dev

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d769326f-d37f-4b72-aca4-eece2ef169c0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lead-identity-members-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

project_id, user.user_id, "project.lead_changed", {"member_id": body.member_id}
)
members = await store.list_members(project_id)
_mirror(request, {**p, "members": members})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Filesystem mirror is written with the stale, pre-change lead_member_id

p is fetched at line 318, before set_lead updates the DB, so {**p, "members": members} still carries the old lead_member_id. write_project_yaml (folders.py:28) serializes the entire payload, so the on-disk project.yaml will reflect the previous lead after every change. The DB remains authoritative, but any consumer reading project.yaml (e.g. agent/file access) sees a divergent lead. Re-fetch the project after set_lead (or patch p["lead_member_id"]) before calling _mirror.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

(val, project_id, member_id),
async def _backfill_lead_member_id(self) -> None:
rows = await (await self._db.execute(
"SELECT id FROM projects WHERE lead_member_id IS NULL"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Backfill can resurrect a deliberately-cleared lead

The backfill keys only on lead_member_id IS NULL and re-derives the lead from the legacy is_lead = 1 flag. set_lead never clears is_lead, so after a user intentionally clears the lead (set_lead(None)lead_member_id = NULL), the next process restart re-runs _backfill_lead_member_id, finds flagged of length 1, and re-populates lead_member_id from the stale flag — silently undoing the user's "No lead" choice. Track migration state separately (e.g. a lead_migrated flag, or clear is_lead during migration) so intentional clears are not reverted.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

class LeadIn(BaseModel):
is_lead: bool
class ProjectLeadIn(BaseModel):
member_id: "str | None" = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: An empty request body silently clears the lead

member_id defaults to None, so a PATCH /api/projects/{pid}/lead with an empty {} body clears the lead instead of erroring. Consider making member_id required (no default) so an unintended empty payload is rejected with 422 rather than wiping the lead.

Suggested change
member_id: "str | None" = None
member_id: "str | None"

Reply with @kilocode-bot fix it to have Kilo Code address this issue.



@pytest.mark.asyncio
async def test_set_lead_exclusive_replaces_previous(client):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Test name/body overclaims exclusivity coverage

test_set_lead_exclusive_replaces_previous never sets two distinct members: it sets a1, clears it, then sets a1 again. It does not actually verify that promoting a second member unsets the first. Cross-member exclusivity is only asserted in tests/test_routes_projects.py::test_set_lead_exclusive_leaves_only_last. Rename this test or add a second distinct member so the body matches the docstring.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

// Revert on failure so the UI stays consistent with the server.
setLeadMemberId(project.lead_member_id ?? null);
}
onChanged();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: onChanged() fires even when the lead change failed

After the catch reverts local leadMemberId state, onChanged() is still called unconditionally, triggering an unnecessary parent refresh/flicker on failure. Consider calling onChanged() only on success (or moving it inside the try) so a failed request doesn't cause a spurious re-render.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/routes/projects.py 330 Filesystem mirror written with stale pre-change lead_member_id (diverges from DB)
tinyagentos/projects/project_store.py 91 Backfill re-derives lead from legacy is_lead flag and can resurrect a deliberately-cleared lead on restart

SUGGESTION

File Line Issue
tinyagentos/routes/projects.py 300 member_id defaults to None; empty body silently clears the lead
tests/projects/test_routes_a2a.py 163 test_set_lead_exclusive_replaces_previous name/body overclaims exclusivity coverage (never sets two distinct members)
desktop/src/apps/ProjectsApp/ProjectMembers.tsx 208 onChanged() fires even when the lead change failed
Files Reviewed (11 files)
  • desktop/src/apps/ProjectsApp/ProjectMembers.test.tsx
  • desktop/src/apps/ProjectsApp/ProjectMembers.tsx
  • desktop/src/apps/ProjectsApp/canvas/canvas-api.ts
  • desktop/src/lib/projects.test.ts
  • desktop/src/lib/projects.ts
  • tests/projects/test_a2a.py
  • tests/projects/test_routes_a2a.py
  • tests/test_routes_projects.py
  • tinyagentos/projects/a2a.py
  • tinyagentos/projects/project_store.py
  • tinyagentos/routes/projects.py

Fix these issues in Kilo Cloud


Reviewed by hy3:free · Input: 100.5K · Output: 16.6K · Cached: 441.7K

@jaylfc

jaylfc commented Jul 15, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #1838, which landed all seven slices as one consistent set on dev (with the CI-caught active-handle migration-order fix folded). Closing.

@jaylfc jaylfc closed this Jul 15, 2026
@jaylfc jaylfc deleted the feat/lead-identity-members-ui branch July 15, 2026 23:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant