From 86c9d78e771cb9122971721f7d5963ccd05b14a3 Mon Sep 17 00:00:00 2001 From: pikann Date: Tue, 12 May 2026 14:50:02 +0700 Subject: [PATCH 1/4] feat: enhance activity logging for checklist and item operations Co-authored-by: Copilot --- backend/checklists.go | 23 +++++++++++++++++++++++ backend/go.mod | 2 +- backend/go.sum | 4 ++-- backend/items.go | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/backend/checklists.go b/backend/checklists.go index 0d4c4aa..6c7c74a 100644 --- a/backend/checklists.go +++ b/backend/checklists.go @@ -162,6 +162,8 @@ func (p *checklistPlugin) createChecklist(req *plugin.Request, res *plugin.Respo CreatedAt: now, UpdatedAt: now, } + plugin.RecordActivity(taskID, projectID, req.Caller.UserID, "task.checklist.created", + map[string]any{"title": b.Title, "_description": "created checklist: \"" + b.Title + "\""}) created(res, cl) } @@ -230,6 +232,8 @@ func (p *checklistPlugin) updateChecklist(req *plugin.Request, res *plugin.Respo CreatedAt: sc.str("created_at"), UpdatedAt: now, } + plugin.RecordActivity(taskID, projectID, req.Caller.UserID, "task.checklist.updated", + map[string]any{"title": *b.Title, "_description": "renamed checklist to \"" + *b.Title + "\""}) ok(res, cl) } @@ -243,6 +247,23 @@ func (p *checklistPlugin) deleteChecklist(req *plugin.Request, res *plugin.Respo return } + // Fetch title before deletion so it can be included in the activity record. + titleResult, err := p.db.Query( + `SELECT title FROM task_checklists WHERE id = $1 AND task_id = $2`, + checklistID, taskID, + ) + if err != nil { + p.log.Error("deleteChecklist title fetch: " + err.Error()) + res.Error(500, "failed to delete checklist") + return + } + if len(titleResult.Rows) == 0 { + res.Error(404, "checklist not found") + return + } + titleSC := newRowScanner(titleResult.Columns, titleResult.Rows[0]) + clTitle := titleSC.str("title") + affected, err := p.db.Exec( `DELETE FROM task_checklists WHERE id = $1 AND task_id = $2`, checklistID, taskID, @@ -256,6 +277,8 @@ func (p *checklistPlugin) deleteChecklist(req *plugin.Request, res *plugin.Respo res.Error(404, "checklist not found") return } + plugin.RecordActivity(taskID, projectID, req.Caller.UserID, "task.checklist.deleted", + map[string]any{"title": clTitle, "_description": "deleted checklist: \"" + clTitle + "\""}) res.NoContent() } diff --git a/backend/go.mod b/backend/go.mod index 85fad87..80a998f 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -2,6 +2,6 @@ module github.com/Paca-AI/first-party/checklist go 1.24 -require github.com/Paca-AI/plugin-sdk-go v0.2.0-rc.3 +require github.com/Paca-AI/plugin-sdk-go v0.2.0-rc.5 require github.com/google/uuid v1.6.0 diff --git a/backend/go.sum b/backend/go.sum index 664e182..344bb56 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,4 +1,4 @@ -github.com/Paca-AI/plugin-sdk-go v0.2.0-rc.3 h1:2MFHmdVIKVo1/Cyo7GL3S/LsAFxpsqhJSAbTao+xNEw= -github.com/Paca-AI/plugin-sdk-go v0.2.0-rc.3/go.mod h1:5WeC6cSEf2wM1ovICZbDaVky9oi5id/Qpdfc5LDAQnw= +github.com/Paca-AI/plugin-sdk-go v0.2.0-rc.5 h1:xrEPnJLM2sdwtY/wRaGOdfeDPv5zPUpR8QZJ/w2nQVU= +github.com/Paca-AI/plugin-sdk-go v0.2.0-rc.5/go.mod h1:5WeC6cSEf2wM1ovICZbDaVky9oi5id/Qpdfc5LDAQnw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/backend/items.go b/backend/items.go index 5c87945..275aa93 100644 --- a/backend/items.go +++ b/backend/items.go @@ -67,6 +67,8 @@ func (p *checklistPlugin) createItem(req *plugin.Request, res *plugin.Response) CreatedAt: now, UpdatedAt: now, } + plugin.RecordActivity(taskID, projectID, req.Caller.UserID, "task.checklist_item.created", + map[string]any{"text": b.Title, "_description": "added checklist item: \"" + b.Title + "\""}) created(res, item) } @@ -161,6 +163,19 @@ func (p *checklistPlugin) updateItem(req *plugin.Request, res *plugin.Response) CreatedAt: createdAt, UpdatedAt: now, } + // Build change list for the activity record. + var changes []map[string]any + if b.Title != nil && *b.Title != sc.str("title") { + changes = append(changes, map[string]any{"field": "title", "old": sc.str("title"), "new": *b.Title}) + } + if b.IsChecked != nil && *b.IsChecked != sc.boolVal("is_checked") { + changes = append(changes, map[string]any{"field": "is_checked", "old": sc.boolVal("is_checked"), "new": *b.IsChecked}) + } + if b.AssigneeID != nil { + changes = append(changes, map[string]any{"field": "assignee_id", "old": sc.strPtr("assignee_id"), "new": *b.AssigneeID}) + } + plugin.RecordActivity(taskID, projectID, req.Caller.UserID, "task.checklist_item.updated", + map[string]any{"text": updTitle, "changes": changes, "_description": "updated checklist item: \"" + updTitle + "\""}) ok(res, item) } @@ -175,6 +190,23 @@ func (p *checklistPlugin) deleteItem(req *plugin.Request, res *plugin.Response) return } + // Fetch item title before deletion for activity record. + titleResult, err := p.db.Query( + `SELECT title FROM task_checklist_items WHERE id = $1 AND checklist_id = $2`, + itemID, checklistID, + ) + if err != nil { + p.log.Error("deleteItem title fetch: " + err.Error()) + res.Error(500, "failed to delete item") + return + } + if len(titleResult.Rows) == 0 { + res.Error(404, "item not found") + return + } + itemTitleSC := newRowScanner(titleResult.Columns, titleResult.Rows[0]) + itemTitle := itemTitleSC.str("title") + affected, err := p.db.Exec( `DELETE FROM task_checklist_items WHERE id = $1 AND checklist_id = $2`, itemID, checklistID, @@ -188,5 +220,7 @@ func (p *checklistPlugin) deleteItem(req *plugin.Request, res *plugin.Response) res.Error(404, "item not found") return } + plugin.RecordActivity(taskID, projectID, req.Caller.UserID, "task.checklist_item.deleted", + map[string]any{"text": itemTitle, "_description": "removed checklist item: \"" + itemTitle + "\""}) res.NoContent() } From b1894eb43351eb731f376576b2e2486f7c2fddf6 Mon Sep 17 00:00:00 2001 From: pikann Date: Tue, 12 May 2026 14:50:09 +0700 Subject: [PATCH 2/4] feat: add CI workflows for backend, frontend, and MCP with typechecking and build steps --- .github/workflows/backend-pr-ci.yml | 101 +++++++++++++++++++++++++++ .github/workflows/frontend-pr-ci.yml | 53 ++++++++++++++ .github/workflows/mcp-pr-ci.yml | 53 ++++++++++++++ plugin.json | 2 +- 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/backend-pr-ci.yml create mode 100644 .github/workflows/frontend-pr-ci.yml create mode 100644 .github/workflows/mcp-pr-ci.yml diff --git a/.github/workflows/backend-pr-ci.yml b/.github/workflows/backend-pr-ci.yml new file mode 100644 index 0000000..64f18b9 --- /dev/null +++ b/.github/workflows/backend-pr-ci.yml @@ -0,0 +1,101 @@ +name: backend-pr-ci + +on: + pull_request: + paths: + - "backend/**" + - ".github/workflows/backend-pr-ci.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: backend-pr-ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + GO_VERSION: "1.24" + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 10 + + defaults: + run: + working-directory: backend + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: backend/go.sum + + - name: Install golangci-lint + run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest + + - name: Run golangci-lint + run: golangci-lint run --timeout=5m + + build: + name: Build + runs-on: ubuntu-latest + timeout-minutes: 10 + + defaults: + run: + working-directory: backend + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: backend/go.sum + + - name: Build WASM + run: GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o checklist.wasm . + + test: + name: Tests + runs-on: ubuntu-latest + timeout-minutes: 15 + + defaults: + run: + working-directory: backend + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: backend/go.sum + + - name: Run tests (race detector) + run: go test -race -timeout 60s -coverprofile=coverage.out $(go list ./...) + + - name: Upload coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: backend/coverage.out + retention-days: 7 diff --git a/.github/workflows/frontend-pr-ci.yml b/.github/workflows/frontend-pr-ci.yml new file mode 100644 index 0000000..8798bad --- /dev/null +++ b/.github/workflows/frontend-pr-ci.yml @@ -0,0 +1,53 @@ +name: frontend-pr-ci + +on: + pull_request: + paths: + - "frontend/**" + - ".github/workflows/frontend-pr-ci.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: frontend-pr-ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + web-quality: + name: Typecheck and build frontend + runs-on: ubuntu-latest + timeout-minutes: 20 + + defaults: + run: + working-directory: frontend + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.2.23" + + - name: Cache Bun packages + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('frontend/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Typecheck + run: bun run typecheck + + - name: Build production bundle + run: bun run build diff --git a/.github/workflows/mcp-pr-ci.yml b/.github/workflows/mcp-pr-ci.yml new file mode 100644 index 0000000..1429198 --- /dev/null +++ b/.github/workflows/mcp-pr-ci.yml @@ -0,0 +1,53 @@ +name: mcp-pr-ci + +on: + pull_request: + paths: + - "mcp/**" + - ".github/workflows/mcp-pr-ci.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: mcp-pr-ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + mcp-quality: + name: Typecheck and build MCP server + runs-on: ubuntu-latest + timeout-minutes: 10 + + defaults: + run: + working-directory: mcp + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.2.23" + + - name: Cache Bun packages + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-mcp-${{ hashFiles('mcp/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun-mcp- + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Typecheck + run: bun run typecheck + + - name: Build + run: bun run build diff --git a/plugin.json b/plugin.json index 02e39ae..d221b69 100644 --- a/plugin.json +++ b/plugin.json @@ -2,7 +2,7 @@ "id": "com.paca.checklist", "displayName": "Checklist", "description": "Adds named checklists with checkable items to tasks.", - "version": "0.1.0", + "version": "0.2.1", "permissions": ["db.read", "db.write", "events.subscribe"], "backend": { "eventSubscriptions": ["task.deleted"], From 6c7e2357d5f2aa17aa809efd3b1fdf854ff14945 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 08:01:47 +0000 Subject: [PATCH 3/4] fix: normalize checklist item update activity changes payload Agent-Logs-Url: https://github.com/Paca-AI/paca-plugin-checklist/sessions/d5f85df3-737f-4cc6-8aa0-975098580c00 Co-authored-by: pikann <41873019+pikann@users.noreply.github.com> --- backend/items.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/backend/items.go b/backend/items.go index 275aa93..9f7ebd5 100644 --- a/backend/items.go +++ b/backend/items.go @@ -164,15 +164,22 @@ func (p *checklistPlugin) updateItem(req *plugin.Request, res *plugin.Response) UpdatedAt: now, } // Build change list for the activity record. - var changes []map[string]any - if b.Title != nil && *b.Title != sc.str("title") { - changes = append(changes, map[string]any{"field": "title", "old": sc.str("title"), "new": *b.Title}) - } - if b.IsChecked != nil && *b.IsChecked != sc.boolVal("is_checked") { - changes = append(changes, map[string]any{"field": "is_checked", "old": sc.boolVal("is_checked"), "new": *b.IsChecked}) - } - if b.AssigneeID != nil { - changes = append(changes, map[string]any{"field": "assignee_id", "old": sc.strPtr("assignee_id"), "new": *b.AssigneeID}) + changes := make([]map[string]any, 0, 3) + oldTitle := sc.str("title") + if b.Title != nil && *b.Title != oldTitle { + changes = append(changes, map[string]any{"field": "title", "old": oldTitle, "new": *b.Title}) + } + oldChecked := sc.boolVal("is_checked") + if b.IsChecked != nil && *b.IsChecked != oldChecked { + changes = append(changes, map[string]any{"field": "is_checked", "old": oldChecked, "new": *b.IsChecked}) + } + oldAssignee := sc.strPtr("assignee_id") + if b.AssigneeID != nil && (oldAssignee == nil || *oldAssignee != *b.AssigneeID) { + var oldAssigneeActivity any + if oldAssignee != nil { + oldAssigneeActivity = *oldAssignee + } + changes = append(changes, map[string]any{"field": "assignee_id", "old": oldAssigneeActivity, "new": *b.AssigneeID}) } plugin.RecordActivity(taskID, projectID, req.Caller.UserID, "task.checklist_item.updated", map[string]any{"text": updTitle, "changes": changes, "_description": "updated checklist item: \"" + updTitle + "\""}) From 3fdb748e99e6d43fe3d8ee252a03bbb063865f6f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 08:02:59 +0000 Subject: [PATCH 4/4] chore: simplify changes slice initialization in item activity logging Agent-Logs-Url: https://github.com/Paca-AI/paca-plugin-checklist/sessions/d5f85df3-737f-4cc6-8aa0-975098580c00 Co-authored-by: pikann <41873019+pikann@users.noreply.github.com> --- backend/items.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/items.go b/backend/items.go index 9f7ebd5..95cd9a1 100644 --- a/backend/items.go +++ b/backend/items.go @@ -164,7 +164,7 @@ func (p *checklistPlugin) updateItem(req *plugin.Request, res *plugin.Response) UpdatedAt: now, } // Build change list for the activity record. - changes := make([]map[string]any, 0, 3) + changes := []map[string]any{} oldTitle := sc.str("title") if b.Title != nil && *b.Title != oldTitle { changes = append(changes, map[string]any{"field": "title", "old": oldTitle, "new": *b.Title})