From a685ae3e6c6f61efc98dbf13085b933113c0c627 Mon Sep 17 00:00:00 2001 From: iliya Date: Sun, 15 Mar 2026 20:18:28 +0200 Subject: [PATCH] feat: add remark-stringify for markdown processing and update logging in TeamMemberLogsFinder - Added `remark-stringify` to the project for converting markdown to plain text. - Updated the text formatting pipeline to include `remark-stringify` for improved markdown handling. - Commented out performance logging in `TeamMemberLogsFinder` to reduce console output during execution. - Enhanced the `getUnreadCount` and `getLegacyCutoff` functions in `commentReadStorage` to clarify legacy comment handling logic. --- package.json | 1 + pnpm-lock.yaml | 3 ++ .../services/team/TeamMemberLogsFinder.ts | 24 ++++++------- src/main/utils/textFormatting.ts | 4 ++- src/renderer/services/commentReadStorage.ts | 34 ++++++++++++++----- 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 8470c20e..07b39bd8 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,7 @@ "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", "simple-git": "^3.32.3", "ssh-config": "^5.0.4", "ssh2": "^1.17.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2d63940..5271fe5f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -254,6 +254,9 @@ importers: remark-parse: specifier: ^11.0.0 version: 11.0.0 + remark-stringify: + specifier: ^11.0.0 + version: 11.0.0 simple-git: specifier: ^3.32.3 version: 3.32.3 diff --git a/src/main/services/team/TeamMemberLogsFinder.ts b/src/main/services/team/TeamMemberLogsFinder.ts index b9e43ff8..5db27d90 100644 --- a/src/main/services/team/TeamMemberLogsFinder.ts +++ b/src/main/services/team/TeamMemberLogsFinder.ts @@ -412,9 +412,9 @@ export class TeamMemberLogsFinder { const tDiscovery = performance.now(); if (!discovery) { - console.log( - `[perf] findLogFileRefsForTask(${taskId}) discovery=null ${(tDiscovery - t0).toFixed(0)}ms` - ); + // console.log( + // `[perf] findLogFileRefsForTask(${taskId}) discovery=null ${(tDiscovery - t0).toFixed(0)}ms` + // ); return []; } @@ -581,15 +581,15 @@ export class TeamMemberLogsFinder { const sortedRefs = [...refs].sort((a, b) => b.sortTime - a.sortTime); const tTotal = performance.now(); - console.log( - `[perf] findLogFileRefsForTask(${taskId}@${teamName}) ` + - `total=${(tTotal - t0).toFixed(0)}ms | ` + - `discovery=${(tDiscovery - t0).toFixed(0)}ms | ` + - `lead=${(tLead - tDiscovery).toFixed(0)}ms | ` + - `scan=${(tScan - tLead).toFixed(0)}ms (${totalFiles} files, ${mentionHits} hits) | ` + - `owner=${(tOwner - tScan).toFixed(0)}ms | ` + - `sessions=${sessionIds.length} | results=${sortedRefs.length}` - ); + // console.log( + // `[perf] findLogFileRefsForTask(${taskId}@${teamName}) ` + + // `total=${(tTotal - t0).toFixed(0)}ms | ` + + // `discovery=${(tDiscovery - t0).toFixed(0)}ms | ` + + // `lead=${(tLead - tDiscovery).toFixed(0)}ms | ` + + // `scan=${(tScan - tLead).toFixed(0)}ms (${totalFiles} files, ${mentionHits} hits) | ` + + // `owner=${(tOwner - tScan).toFixed(0)}ms | ` + + // `sessions=${sessionIds.length} | results=${sortedRefs.length}` + // ); return sortedRefs.map(({ filePath, memberName }) => ({ filePath, memberName })); } diff --git a/src/main/utils/textFormatting.ts b/src/main/utils/textFormatting.ts index c22f6d34..74fd96e2 100644 --- a/src/main/utils/textFormatting.ts +++ b/src/main/utils/textFormatting.ts @@ -1,14 +1,16 @@ import remarkParse from 'remark-parse'; +import remarkStringify from 'remark-stringify'; import stripMarkdownPlugin from 'strip-markdown'; import { unified } from 'unified'; -const processor = unified().use(remarkParse).use(stripMarkdownPlugin); +const processor = unified().use(remarkParse).use(stripMarkdownPlugin).use(remarkStringify); /** * Strips markdown formatting from text for use in plain-text contexts * like native OS notifications. * * Uses remark ecosystem (strip-markdown plugin) for reliable parsing. + * Pipeline: remarkParse → stripMarkdown (transform) → remarkStringify (compile to plain text). */ export function stripMarkdown(text: string): string { const result = processor.processSync(text); diff --git a/src/renderer/services/commentReadStorage.ts b/src/renderer/services/commentReadStorage.ts index b3a62201..459932b4 100644 --- a/src/renderer/services/commentReadStorage.ts +++ b/src/renderer/services/commentReadStorage.ts @@ -162,9 +162,14 @@ export function markAsRead(teamName: string, taskId: string, latestTimestamp: nu /** * Count unread comments for a task. - * A comment is unread if: - * 1. Its ID is NOT in the readIds set, AND - * 2. Its timestamp is AFTER the lastUpdated migration marker (for legacy data) + * A comment is unread if its ID is NOT in the readIds set. + * + * Legacy migration: when readIds is empty (data migrated from v1 timestamp + * format), comments created at or before the legacy cutoff are treated as read. + * Once any per-ID tracking starts (readIds non-empty), the cutoff is ignored + * — only explicit IDs determine read state. This prevents `lastUpdated` + * (which is refreshed by markCommentsRead on every save for stale-cleanup + * purposes) from accidentally marking ALL comments as read. */ export function getUnreadCount( readState: ReadState, @@ -178,15 +183,20 @@ export function getUnreadCount( if (!entry) return comments.length; const readSet = new Set(entry.readIds); - const legacyCutoff = entry.lastUpdated; + // Only use the timestamp cutoff for pure-legacy entries (no per-ID tracking yet). + // Once readIds is non-empty, per-ID tracking is authoritative and the timestamp + // must NOT be used — it gets refreshed to Date.now() on every save. + const legacyCutoff = readSet.size === 0 ? entry.lastUpdated : 0; let count = 0; for (const c of comments) { // If comment has an ID and it's in the read set → read if (c.id && readSet.has(c.id)) continue; - // If comment was created before/at the legacy cutoff → read (migrated data) - const ts = new Date(c.createdAt).getTime(); - if (legacyCutoff > 0 && ts <= legacyCutoff) continue; + // Legacy-only: comment created before/at the migration cutoff → read + if (legacyCutoff > 0) { + const ts = new Date(c.createdAt).getTime(); + if (ts <= legacyCutoff) continue; + } // Otherwise → unread count++; } @@ -204,10 +214,18 @@ export function getReadCommentIds(teamName: string, taskId: string): Set /** * Get the legacy migration cutoff timestamp for a team/task pair (0 if none). + * Returns non-zero only for pure-legacy entries where readIds is empty. + * Once per-ID tracking has started (readIds non-empty), the cutoff is 0 + * because lastUpdated gets refreshed to Date.now() on every save and + * would incorrectly mark all comments as read. */ export function getLegacyCutoff(teamName: string, taskId: string): number { const key = `${teamName}/${taskId}`; - return cache[key]?.lastUpdated ?? 0; + const entry = cache[key]; + if (!entry) return 0; + // Only honour the timestamp when no per-ID tracking exists (pure legacy data). + if (entry.readIds.length > 0) return 0; + return entry.lastUpdated; } /** @deprecated Use getReadCommentIds() + getLegacyCutoff() instead. */