From 51a32f323dd79bf28c9926016310ab50cb624570 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Apr 2026 20:14:23 +0000
Subject: [PATCH 1/6] feat: track actual comment edits separately
Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/2c04736d-02c3-4486-be2e-8cbbe3547894
Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com>
---
frontend/cypress/e2e/comment.cy.js | 2 ++
frontend/src/components/Comment.vue | 6 +++---
frontend/src/components/CommentsArea.vue | 11 +++++-----
frontend/src/components/CommentsList.vue | 3 ++-
frontend/src/types/doctypes.ts | 2 ++
.../doctype/gp_comment/gp_comment.json | 6 ++++++
.../gameplan/doctype/gp_comment/gp_comment.py | 8 ++++++++
gameplan/patches.txt | 1 +
.../patches/backfill_gp_comment_edited_at.py | 20 +++++++++++++++++++
9 files changed, 50 insertions(+), 9 deletions(-)
create mode 100644 gameplan/patches/backfill_gp_comment_edited_at.py
diff --git a/frontend/cypress/e2e/comment.cy.js b/frontend/cypress/e2e/comment.cy.js
index 64b945843..d1a35fce6 100644
--- a/frontend/cypress/e2e/comment.cy.js
+++ b/frontend/cypress/e2e/comment.cy.js
@@ -64,6 +64,7 @@ describe('Comment', () => {
cy.get('button:contains("👍"):visible').click()
cy.wait('@reactRequest')
cy.get('button:contains("👍 1")').should('exist')
+ cy.get(`div[data-id=${comment.name}]`).contains('Edited').should('not.exist')
// remove a reaction
cy.get(`div[data-id=${comment.name}] button[aria-label="Add a reaction"]`).click()
@@ -82,6 +83,7 @@ describe('Comment', () => {
.type('{enter}@john{enter}', { delay: 100 }) // mention user
cy.button('Submit').click()
cy.get(`div[data-id=${comment.name}]`).contains('This is an edited comment').should('exist')
+ cy.get(`div[data-id=${comment.name}]`).contains('Edited').should('exist')
cy.get(
`div[data-id=${comment.name}] [data-type="mention"][data-id="john@example.com"]`,
).should('exist')
diff --git a/frontend/src/components/Comment.vue b/frontend/src/components/Comment.vue
index 0a58690e0..989296695 100644
--- a/frontend/src/components/Comment.vue
+++ b/frontend/src/components/Comment.vue
@@ -23,8 +23,8 @@
· Edited
@@ -180,7 +180,7 @@ const dropdownOptions = computed(() => [
label: 'Revisions',
icon: 'rotate-ccw',
onClick: () => (showRevisionsDialog.value = true),
- condition: () => props.comment.modified > props.comment.creation,
+ condition: () => Boolean(props.comment.edited_at),
},
{
label: 'Copy link',
diff --git a/frontend/src/components/CommentsArea.vue b/frontend/src/components/CommentsArea.vue
index bdb562b4c..576142536 100644
--- a/frontend/src/components/CommentsArea.vue
+++ b/frontend/src/components/CommentsArea.vue
@@ -222,11 +222,12 @@ const comments = useList({
'name',
'content',
'owner',
- 'creation',
- 'modified',
- 'deleted_at',
- { reactions: ['name', 'user', 'emoji'] },
- ],
+ 'creation',
+ 'modified',
+ 'edited_at',
+ 'deleted_at',
+ { reactions: ['name', 'user', 'emoji'] },
+ ],
transform(data) {
return data.map((d) => ({ ...d, doctype: 'GP Comment' }))
},
diff --git a/frontend/src/components/CommentsList.vue b/frontend/src/components/CommentsList.vue
index 9e1560e1b..97684f1be 100644
--- a/frontend/src/components/CommentsList.vue
+++ b/frontend/src/components/CommentsList.vue
@@ -130,7 +130,7 @@ const newCommentEditor = ref(null)
const comments = useList<
Pick<
GPComment,
- 'name' | 'content' | 'owner' | 'creation' | 'modified' | 'deleted_at' | 'reactions'
+ 'name' | 'content' | 'owner' | 'creation' | 'modified' | 'edited_at' | 'deleted_at' | 'reactions'
>
>({
doctype: 'GP Comment',
@@ -141,6 +141,7 @@ const comments = useList<
'owner',
'creation',
'modified',
+ 'edited_at',
'deleted_at',
{ reactions: ['name', 'user', 'emoji'] },
],
diff --git a/frontend/src/types/doctypes.ts b/frontend/src/types/doctypes.ts
index ad2065bbd..0d0b574a8 100644
--- a/frontend/src/types/doctypes.ts
+++ b/frontend/src/types/doctypes.ts
@@ -257,6 +257,8 @@ export interface GPComment extends DocType {
reference_doctype?: string
/** Reference Name: Dynamic Link (reference_doctype) */
reference_name?: string
+ /** Edited At: Datetime */
+ edited_at?: string
/** Reactions: Table (GP Reaction) */
reactions: GPReaction[]
/** Deleted At: Datetime */
diff --git a/gameplan/gameplan/doctype/gp_comment/gp_comment.json b/gameplan/gameplan/doctype/gp_comment/gp_comment.json
index b1b9e8112..1068a844f 100644
--- a/gameplan/gameplan/doctype/gp_comment/gp_comment.json
+++ b/gameplan/gameplan/doctype/gp_comment/gp_comment.json
@@ -9,6 +9,7 @@
"content",
"reference_doctype",
"reference_name",
+ "edited_at",
"deleted_at",
"reactions",
"tags"
@@ -34,6 +35,11 @@
"label": "Reference Name",
"options": "reference_doctype"
},
+ {
+ "fieldname": "edited_at",
+ "fieldtype": "Datetime",
+ "label": "Edited At"
+ },
{
"fieldname": "reactions",
"fieldtype": "Table",
diff --git a/gameplan/gameplan/doctype/gp_comment/gp_comment.py b/gameplan/gameplan/doctype/gp_comment/gp_comment.py
index 206370efc..e8470f494 100644
--- a/gameplan/gameplan/doctype/gp_comment/gp_comment.py
+++ b/gameplan/gameplan/doctype/gp_comment/gp_comment.py
@@ -41,6 +41,7 @@ def after_delete(self):
self.update_task_meta()
def before_save(self):
+ self.set_edited_at()
self.update_tags()
def update_discussion_meta(self):
@@ -68,6 +69,13 @@ def sanitize_content(self):
self.content = remove_empty_trailing_paragraphs(self.content)
self.content = sanitize_content(self.content)
+ def set_edited_at(self):
+ if self.is_new() or not self.get_doc_before_save():
+ return
+
+ if self.has_value_changed("content"):
+ self.edited_at = frappe.utils.now()
+
def on_update(self):
self.notify_mentions()
self.notify_reactions()
diff --git a/gameplan/patches.txt b/gameplan/patches.txt
index 0c2dec288..be920719c 100644
--- a/gameplan/patches.txt
+++ b/gameplan/patches.txt
@@ -29,3 +29,4 @@ gameplan.gameplan.doctype.gp_project_visit.patches.add_indexes
gameplan.gameplan.doctype.gp_unread_record.patches.migrate_to_unread_records
gameplan.gameplan.doctype.gp_pinned_project.patches.delete_pinned_projects_for_archived_spaces
gameplan.gameplan.doctype.gp_discussion.patches.set_default_pin_scope
+gameplan.patches.backfill_gp_comment_edited_at
diff --git a/gameplan/patches/backfill_gp_comment_edited_at.py b/gameplan/patches/backfill_gp_comment_edited_at.py
new file mode 100644
index 000000000..59cc50071
--- /dev/null
+++ b/gameplan/patches/backfill_gp_comment_edited_at.py
@@ -0,0 +1,20 @@
+import frappe
+
+
+def execute():
+ versions = frappe.qb.get_query(
+ "Version",
+ fields=["docname", "creation"],
+ filters={
+ "ref_doctype": "GP Comment",
+ "data": ["like", '%["content",%'],
+ },
+ order_by="creation asc",
+ ).run(as_dict=True)
+
+ latest_revisions = {}
+ for version in versions:
+ latest_revisions[version.docname] = version.creation
+
+ for comment, edited_at in latest_revisions.items():
+ frappe.db.set_value("GP Comment", comment, "edited_at", edited_at, update_modified=False)
From 5efb2b35fa06b9cd3071e0f12a9cb12501f30c96 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Apr 2026 20:15:02 +0000
Subject: [PATCH 2/6] style: format comment edit tracking changes
Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/2c04736d-02c3-4486-be2e-8cbbe3547894
Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com>
---
frontend/src/components/CommentsArea.vue | 12 ++++++------
gameplan/patches/backfill_gp_comment_edited_at.py | 3 ++-
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/frontend/src/components/CommentsArea.vue b/frontend/src/components/CommentsArea.vue
index 576142536..413020f84 100644
--- a/frontend/src/components/CommentsArea.vue
+++ b/frontend/src/components/CommentsArea.vue
@@ -222,12 +222,12 @@ const comments = useList({
'name',
'content',
'owner',
- 'creation',
- 'modified',
- 'edited_at',
- 'deleted_at',
- { reactions: ['name', 'user', 'emoji'] },
- ],
+ 'creation',
+ 'modified',
+ 'edited_at',
+ 'deleted_at',
+ { reactions: ['name', 'user', 'emoji'] },
+ ],
transform(data) {
return data.map((d) => ({ ...d, doctype: 'GP Comment' }))
},
diff --git a/gameplan/patches/backfill_gp_comment_edited_at.py b/gameplan/patches/backfill_gp_comment_edited_at.py
index 59cc50071..21940654a 100644
--- a/gameplan/patches/backfill_gp_comment_edited_at.py
+++ b/gameplan/patches/backfill_gp_comment_edited_at.py
@@ -17,4 +17,5 @@ def execute():
latest_revisions[version.docname] = version.creation
for comment, edited_at in latest_revisions.items():
- frappe.db.set_value("GP Comment", comment, "edited_at", edited_at, update_modified=False)
+ if frappe.db.exists("GP Comment", comment):
+ frappe.db.set_value("GP Comment", comment, "edited_at", edited_at, update_modified=False)
From f903fc52ab6510bdb4c1076f18122c7f54252772 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Apr 2026 20:15:34 +0000
Subject: [PATCH 3/6] style: format comments list changes
Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/2c04736d-02c3-4486-be2e-8cbbe3547894
Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com>
---
frontend/src/components/CommentsList.vue | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/frontend/src/components/CommentsList.vue b/frontend/src/components/CommentsList.vue
index 97684f1be..10aca6878 100644
--- a/frontend/src/components/CommentsList.vue
+++ b/frontend/src/components/CommentsList.vue
@@ -130,7 +130,14 @@ const newCommentEditor = ref(null)
const comments = useList<
Pick<
GPComment,
- 'name' | 'content' | 'owner' | 'creation' | 'modified' | 'edited_at' | 'deleted_at' | 'reactions'
+ | 'name'
+ | 'content'
+ | 'owner'
+ | 'creation'
+ | 'modified'
+ | 'edited_at'
+ | 'deleted_at'
+ | 'reactions'
>
>({
doctype: 'GP Comment',
From db1adfdde2f4c86023885e69287d8113099bc3f9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Apr 2026 20:17:59 +0000
Subject: [PATCH 4/6] perf: batch backfill comment edited timestamps
Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/2c04736d-02c3-4486-be2e-8cbbe3547894
Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com>
---
.../patches/backfill_gp_comment_edited_at.py | 32 +++++++++----------
1 file changed, 15 insertions(+), 17 deletions(-)
diff --git a/gameplan/patches/backfill_gp_comment_edited_at.py b/gameplan/patches/backfill_gp_comment_edited_at.py
index 21940654a..ae039e8e1 100644
--- a/gameplan/patches/backfill_gp_comment_edited_at.py
+++ b/gameplan/patches/backfill_gp_comment_edited_at.py
@@ -2,20 +2,18 @@
def execute():
- versions = frappe.qb.get_query(
- "Version",
- fields=["docname", "creation"],
- filters={
- "ref_doctype": "GP Comment",
- "data": ["like", '%["content",%'],
- },
- order_by="creation asc",
- ).run(as_dict=True)
-
- latest_revisions = {}
- for version in versions:
- latest_revisions[version.docname] = version.creation
-
- for comment, edited_at in latest_revisions.items():
- if frappe.db.exists("GP Comment", comment):
- frappe.db.set_value("GP Comment", comment, "edited_at", edited_at, update_modified=False)
+ frappe.db.sql(
+ """
+ update `tabGP Comment` comment
+ inner join (
+ select docname, max(creation) as edited_at
+ from `tabVersion`
+ where ref_doctype = 'GP Comment'
+ and data like %s
+ group by docname
+ ) revisions on revisions.docname = comment.name
+ set comment.edited_at = revisions.edited_at
+ where comment.edited_at is null
+ """,
+ ('%["content",%',),
+ )
From 9c8b579d767c7de3e543f69c648cea0aa3797ef4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Apr 2026 20:19:09 +0000
Subject: [PATCH 5/6] refactor: simplify comment edited tracking
Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/2c04736d-02c3-4486-be2e-8cbbe3547894
Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com>
---
gameplan/gameplan/doctype/gp_comment/gp_comment.py | 2 +-
gameplan/patches/backfill_gp_comment_edited_at.py | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/gameplan/gameplan/doctype/gp_comment/gp_comment.py b/gameplan/gameplan/doctype/gp_comment/gp_comment.py
index e8470f494..86480059e 100644
--- a/gameplan/gameplan/doctype/gp_comment/gp_comment.py
+++ b/gameplan/gameplan/doctype/gp_comment/gp_comment.py
@@ -70,7 +70,7 @@ def sanitize_content(self):
self.content = sanitize_content(self.content)
def set_edited_at(self):
- if self.is_new() or not self.get_doc_before_save():
+ if not self.get_doc_before_save():
return
if self.has_value_changed("content"):
diff --git a/gameplan/patches/backfill_gp_comment_edited_at.py b/gameplan/patches/backfill_gp_comment_edited_at.py
index ae039e8e1..d27d38719 100644
--- a/gameplan/patches/backfill_gp_comment_edited_at.py
+++ b/gameplan/patches/backfill_gp_comment_edited_at.py
@@ -15,5 +15,6 @@ def execute():
set comment.edited_at = revisions.edited_at
where comment.edited_at is null
""",
+ # Version.data stores changed fields as JSON arrays like ["content", old, new].
('%["content",%',),
)
From 4a66c24f5d4ecd026affed0c1d154a97368e12c1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 22 Apr 2026 06:53:40 +0000
Subject: [PATCH 6/6] refactor: move gp comment patch under doctype
Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/cf2a5f48-b6e0-493b-b116-e29e7fd9152e
Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com>
---
.../gp_comment}/patches/backfill_gp_comment_edited_at.py | 0
gameplan/patches.txt | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
rename gameplan/{ => gameplan/doctype/gp_comment}/patches/backfill_gp_comment_edited_at.py (100%)
diff --git a/gameplan/patches/backfill_gp_comment_edited_at.py b/gameplan/gameplan/doctype/gp_comment/patches/backfill_gp_comment_edited_at.py
similarity index 100%
rename from gameplan/patches/backfill_gp_comment_edited_at.py
rename to gameplan/gameplan/doctype/gp_comment/patches/backfill_gp_comment_edited_at.py
diff --git a/gameplan/patches.txt b/gameplan/patches.txt
index be920719c..7f5e73994 100644
--- a/gameplan/patches.txt
+++ b/gameplan/patches.txt
@@ -29,4 +29,4 @@ gameplan.gameplan.doctype.gp_project_visit.patches.add_indexes
gameplan.gameplan.doctype.gp_unread_record.patches.migrate_to_unread_records
gameplan.gameplan.doctype.gp_pinned_project.patches.delete_pinned_projects_for_archived_spaces
gameplan.gameplan.doctype.gp_discussion.patches.set_default_pin_scope
-gameplan.patches.backfill_gp_comment_edited_at
+gameplan.gameplan.doctype.gp_comment.patches.backfill_gp_comment_edited_at