Skip to content

[Feat] Team data model with SQLite persistence and Zustand store#262

Merged
samzong merged 2 commits intomainfrom
feat/team-data-model
Apr 2, 2026
Merged

[Feat] Team data model with SQLite persistence and Zustand store#262
samzong merged 2 commits intomainfrom
feat/team-data-model

Conversation

@samzong
Copy link
Copy Markdown
Collaborator

@samzong samzong commented Apr 2, 2026

Summary

Add a full data layer for Teams: SQLite persistence via Drizzle ORM, IPC CRUD handlers, preload bindings, a Zustand store in core, and UI wiring in TeamsPanel. Teams are now persisted across app restarts.

Type of change

  • [Feat] new feature

Why is this needed?

TeamsPanel (#258) used useState to manage team data — everything was lost on refresh. This PR adds real persistence so teams survive app restarts, and introduces a gateway-bound agent selection flow for team creation.

What changed?

  • Added Team and TeamAgent interfaces to @clawwork/shared
  • Added teams and team_agents SQLite tables (Drizzle schema + raw SQL migration)
  • Added 4 IPC handlers: data:teams-list, data:team-get, data:team-persist, data:team-delete
  • Added 4 preload bindings + type declarations
  • Created team-store.ts in core with full CRUD + agent management actions
  • Created desktop platform binding with useTeamStore hook
  • Wired TeamsPanel to store, replacing useState
  • Updated TeamCard to use shared Team type (agents.length instead of memberCount)
  • Enhanced CreateTeamDialog with gateway selector and agent multi-select
  • Added i18n keys across all 8 locales

Architecture impact

  • Owning layer: shared / main / core / renderer
  • Cross-layer impact: yes — new types flow shared→core→desktop, new IPC channel main↔renderer
  • Invariants touched from docs/architecture-invariants.md: dependency direction (shared←core←desktop)
  • Why those invariants remain protected: types originate in shared, core depends on shared, desktop depends on both — no reverse imports

Linked issues

Closes #258

Validation

  • pnpm lint
  • pnpm test
  • pnpm check:ui-contract
  • pnpm check (full gate — 274 tests pass)
  • Manual smoke test
pnpm check passes: lint, architecture, UI contract, renderer copy, i18n (8 locales), dead code, format, typecheck, all 274 tests

Screenshots or recordings

No layout changes — existing TeamsPanel card grid and CreateTeamDialog are preserved. CreateTeamDialog now includes a gateway selector (shown only when multiple gateways exist) and an agent multi-select list with checkboxes.

Release note

  • User-facing change. Release note is included below.
Teams are now persisted to the local database. Created teams survive app restarts. Team creation now includes gateway selection and agent picking.

Checklist

  • The PR title uses at least one approved prefix: [Feat]
  • The summary explains both what changed and why
  • Validation reflects the commands actually run for this PR
  • Architecture impact is described and references any touched invariants
  • Cross-layer changes are explicitly justified
  • The release note block is accurate

Signed-off-by: samzong <samzong.lu@gmail.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Hi @samzong,
Thanks for your pull request!
If the PR is ready, use the /auto-cc command to assign Reviewer to Review.
We will review it shortly.

Details

Instructions for interacting with me using comments are available here.
If you have questions or suggestions related to my behavior, please file an issue against the gh-ci-bot repository.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust data layer for managing teams within the application. By moving from volatile local state to a persistent SQLite-backed architecture, teams are now reliably saved and retrieved across sessions. The changes span the entire stack, including shared type definitions, backend database schema updates, IPC communication channels, and a centralized state management store, culminating in an improved user experience for team creation and management.

Highlights

  • Data Persistence: Implemented SQLite persistence for teams using Drizzle ORM, ensuring team data survives application restarts.
  • State Management: Introduced a new Zustand store for team management, replacing local component state in the TeamsPanel.
  • IPC Integration: Added new IPC handlers for CRUD operations on teams, enabling communication between the main and renderer processes.
  • UI Enhancements: Updated the CreateTeamDialog to include gateway selection and agent multi-selection, and localized these changes across all supported languages.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 2, 2026

Deploying cpwa with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4f2db4d
Status: ✅  Deploy successful!
Preview URL: https://b2ae4ff4.cpwa.pages.dev
Branch Preview URL: https://feat-team-data-model.cpwa.pages.dev

View logs

Signed-off-by: samzong <samzong.lu@gmail.com>

# Conflicts:
#	packages/desktop/src/renderer/platform/index.ts
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a team management system, featuring a new Zustand store, SQLite database schema updates, and a dedicated UI for team creation and management. The review feedback identifies several areas for improvement: implementing rollback logic for optimistic updates in the team store to maintain state consistency, adding foreign key cascade deletes for database integrity, and addressing a potential race condition in the agent fetching logic within the UI.

Comment on lines +57 to +83
createTeam: async (params) => {
const now = new Date().toISOString();
const id = crypto.randomUUID();
const team: Team = {
id,
...params,
createdAt: now,
updatedAt: now,
};
set((s) => ({ teams: { ...s.teams, [id]: team } }));
const res = await deps.persistTeam({
id: team.id,
name: team.name,
emoji: team.emoji,
description: team.description,
gatewayId: team.gatewayId,
source: team.source,
version: team.version,
agents: team.agents,
createdAt: team.createdAt,
updatedAt: team.updatedAt,
});
if (!res.ok) {
console.error('[team-store] persistTeam failed:', res.error);
}
return id;
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The createTeam action performs an optimistic update by adding the new team to the store before the persistence call completes. However, it lacks a rollback mechanism if deps.persistTeam fails. This can lead to the UI showing data that was never actually saved to the database.

References
  1. Ensure state consistency by handling failures in asynchronous persistence calls, especially when using optimistic updates.

Comment on lines +85 to +106
updateTeam: async (id, updates) => {
const existing = get().teams[id];
if (!existing) return;
const now = new Date().toISOString();
const updated: Team = { ...existing, ...updates, updatedAt: now };
set((s) => ({ teams: { ...s.teams, [id]: updated } }));
const res = await deps.persistTeam({
id: updated.id,
name: updated.name,
emoji: updated.emoji,
description: updated.description,
gatewayId: updated.gatewayId,
source: updated.source,
version: updated.version,
agents: updated.agents,
createdAt: updated.createdAt,
updatedAt: updated.updatedAt,
});
if (!res.ok) {
console.error('[team-store] updateTeam persist failed:', res.error);
}
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Similar to createTeam, the updateTeam action performs an optimistic update without a rollback strategy. If the database persistence fails, the store will remain in an inconsistent state relative to the actual data on disk.

Comment on lines +108 to +118
deleteTeam: async (id) => {
set((s) => {
const next = { ...s.teams };
delete next[id];
return { teams: next };
});
const res = await deps.deleteTeam(id);
if (!res.ok) {
console.error('[team-store] deleteTeam failed:', res.error);
}
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The deleteTeam action removes the team from the store optimistically. If deps.deleteTeam fails, the team should be restored to the store to maintain consistency.


sqlite.exec(`
CREATE TABLE IF NOT EXISTS team_agents (
team_id TEXT NOT NULL REFERENCES teams(id),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The foreign key constraint for team_id in the team_agents table is missing an ON DELETE CASCADE clause. While the IPC handler manually deletes related agents, enforcing this at the database level ensures data integrity and prevents orphaned records if deletions occur through other paths (e.g., raw SQL execution or future refactors).

References
  1. Maintain database integrity by using appropriate foreign key constraints and cascade behaviors. (link)

Comment on lines +76 to +90
useEffect(() => {
if (!open || !gatewayId) return;
setLoadingAgents(true);
setSelectedAgentIds(new Set());
window.clawwork
.listAgents(gatewayId)
.then((res) => {
if (res.ok && res.result) {
const payload = res.result as { agents?: AgentInfo[] };
setAgentCatalog(payload.agents ?? []);
}
})
.catch(() => {})
.finally(() => setLoadingAgents(false));
}, [open, gatewayId]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

There is a potential race condition in this useEffect. If the gatewayId changes rapidly or the dialog is closed before the listAgents promise resolves, setAgentCatalog and setLoadingAgents will still be called for the stale request. Consider using an abort controller or a simple boolean flag to ignore results from outdated effect iterations.

References
  1. Handle race conditions in asynchronous effects to prevent state updates on unmounted components or stale data. (link)

@samzong samzong merged commit 0445f11 into main Apr 2, 2026
14 checks passed
@samzong samzong deleted the feat/team-data-model branch April 2, 2026 17:19
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