Skip to content

feat(tasks): add inline assignees and due dates to page tasks - #4

Merged
crs48 merged 13 commits into
mainfrom
codex/tasks-page-task-sync
Mar 6, 2026
Merged

feat(tasks): add inline assignees and due dates to page tasks#4
crs48 merged 13 commits into
mainfrom
codex/tasks-page-task-sync

Conversation

@crs48

@crs48 crs48 commented Mar 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • add inline @mention assignee chips and due-date chips for page-backed task rows
  • sync assignees and due dates into canonical Task nodes alongside existing task/page/reference reconciliation
  • extend page-task tests and check off the completed items in the universal tasks exploration

Verification

  • pnpm --filter @xnetjs/editor exec vitest run src/extensions/task-metadata/TaskMetadataExtension.test.ts src/extensions/page-tasks/PageTaskItemExtension.test.ts src/extensions/smart-reference/SmartReferenceExtension.test.ts src/components/RichTextEditor.test.tsx
  • pnpm --filter @xnetjs/react exec vitest run src/hooks/usePageTaskSync.test.tsx
  • pnpm --filter @xnetjs/editor typecheck
  • pnpm --filter @xnetjs/react typecheck
  • pnpm --filter xnet-web typecheck
  • pnpm --filter xnet-desktop exec tsc --noEmit
  • git push -u origin codex/tasks-page-task-sync

Notes

  • rich-text page tasks now persist canonical assignee DIDs and inline due dates into Task nodes
  • the original exploration checklist was updated as these tasks landed
  • a stable PR preview URL is not configured yet in this repo; current deploy infrastructure only publishes the main site

Summary by CodeRabbit

  • New Features

    • Embedded task views, per‑page Task panel, My Tasks sidebar panel, multi‑assignee tasks, ExternalReference support, and automatic page↔task synchronization.
  • UI Improvements

    • Inline @mention suggestions with avatars, due‑date chips and toolbar controls, styled smart‑reference chips, hierarchical task lists/embeds, overdue indicators, and task-related toolbar buttons.
  • Documentation

    • Two detailed design explorations on integration strategies and task-as-node architecture.
  • Tests

    • Expanded unit and integration tests for tasks, mentions, due‑dates, smart references, embeds, and sync.

crs48 added 6 commits March 5, 2026 14:22
…ysis

- add exploration 0102 covering AFFiNE BlockSuite integration feasibility

- document architecture fit, risks, and implementation considerations

- preserve findings in docs/explorations for team review
- explore page-native tasks backed by first-class Task nodes
- compare Notion, Linear, AFFiNE, and Obsidian task models
- recommend smart references for GitHub, Figma, YouTube, and similar links
- outline phased implementation, validation, and example schema direction
- add inline smart task references in the editor for supported URLs
- introduce ExternalReferenceSchema for normalized external artifact metadata
- expand TaskSchema with page binding, assignees, source, sort key, and references
- cover editor smart-reference behavior and new data schemas with focused tests
- add a page task item extension that persists task ids and block ids in TipTap rows
- reconcile page task snapshots into Task and ExternalReference nodes for web and desktop page views
- add focused tests and mark completed items in the original task exploration checklist
- add rich-text @mention suggestions that store canonical DIDs in inline task chips

- add inline due-date chips and sync both assignees and due dates into page-backed Task nodes

- extend task metadata tests and check off the completed exploration checklist items
@coderabbitai

coderabbitai Bot commented Mar 6, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds page-backed task primitives, editor task UI (mentions, due dates, task views, smart references), a debounced usePageTaskSync hook reconciling editor snapshots with canonical Task/ExternalReference records, schema/types/tests, gravatar utilities, UI panels (web/electron), and CI workflows for PR previews and site deploys.

Changes

Cohort / File(s) Summary
Editor extensions & features
packages/editor/src/extensions/..., packages/editor/src/components/..., packages/editor/src/styles/editor.css
Adds PageTaskItemExtension, TaskMentionExtension, TaskDueDateExtension, SmartReferenceExtension, TaskViewEmbedExtension, node views, TaskMentionMenu, gravatar utils, and editor CSS for chips. New tiptap commands, node attributes, and exported extension types introduced.
Rich text editor integration
packages/editor/src/components/RichTextEditor.tsx, packages/editor/src/react.ts, packages/editor/src/extensions.ts
RichTextEditor props expanded (renderTaskView, taskViewPageId, mentionSuggestions, onPageTasksChange); integrates page-task extensions, publishes page-task snapshots, and re-exports new types/utilities.
Editor UX & toolbar
packages/editor/src/components/FloatingToolbar.tsx, packages/editor/src/components/editor-ux-state.ts
Toolbar gains task-aware buttons (insert mention, pick due date); desktop toolbar visibility now considers collapsed selection inside task items.
Embed & node views
packages/editor/src/extensions/task-view-embed/..., packages/editor/src/extensions/task-view-embed/TaskViewEmbedNodeView.tsx
Introduces TaskViewEmbed node, DEFAULT_TASK_VIEW_CONFIG, TaskViewEmbedNodeView and renderView hook to embed TaskCollectionEmbed within the editor.
Page task collection & snapshotting
packages/editor/src/extensions/page-tasks/index.ts, packages/editor/src/extensions/page-tasks/*.test.ts
Implements collectPageTasks, ensurePageTaskAttrs, getPageTasksSnapshot and tests to derive stable task snapshots and apply attribute updates in-editor.
React hooks & data layer
packages/react/src/hooks/usePageTaskSync.ts, packages/react/src/hooks/useTasks.ts, packages/react/src/index.ts, packages/react/src/hooks/*.test.tsx
Adds usePageTaskSync (debounced create/update/restore/remove of Task and ExternalReference), useTasks query/filter hook, public exports, and tests. Note side-effecting writes and debounce behavior.
UI components (web & electron)
packages/react/src/components/TaskCollectionEmbed.tsx, apps/web/src/components/PageTasksPanel.tsx, apps/electron/src/renderer/components/PageTasksPanel.tsx, apps/web/src/components/MyTasksPanel.tsx
Adds TaskCollectionEmbed and page/my-tasks panels with filtering, hierarchy, due-date formatting and overdue styling. Note props for currentPageId/currentDid and scope.
App wiring & consumers
apps/web/src/components/Editor.tsx, apps/web/src/routes/doc.$docId.tsx, apps/electron/src/renderer/components/PageView.tsx, apps/web/src/components/Sidebar.tsx
Editor consumers now pass mentionSuggestions, onPageTasksChange, taskViewPageId; pages use usePageTaskSync and render PageTasksPanel; Sidebar includes MyTasksPanel.
Data schemas & tests
packages/data/src/schema/schemas/external-reference.*, packages/data/src/schema/schemas/task.*, packages/data/src/schema/index.ts, packages/data/src/index.ts
Adds ExternalReferenceSchema/type and extends TaskSchema (assignees[], page relation, anchorBlockId, sortKey, source, references relation); registers exports and adds tests.
Smart reference provider & tests
packages/editor/src/extensions/smart-reference/...
Adds parseSmartReferenceUrl, provider parsing (GitHub, Figma, YouTube, embeds), SmartReferenceExtension with paste conversion and tests.
Gravatar utility & tests
packages/editor/src/utils/gravatar.ts, packages/editor/src/utils/gravatar.test.ts
Implements md5 and createGravatarUrl utilities and tests used for avatars in mention suggestions.
Docs & site
docs/explorations/0102_..., docs/explorations/0103_..., site/src/layouts/Base.astro
Adds two exploration design docs and adjusts Astro code token selectors to :global.
CI workflows
.github/workflows/deploy-pr-preview.yml, .github/workflows/remove-pr-preview.yml, .github/workflows/deploy-site.yml, .github/workflows/undeploy-site.yml
Adds PR preview deploy/remove workflows and reworks site deploy flows to use git worktree/rsync and adjusted permissions.
Tests & performance helpers
multiple *.test.tsx/*.test.ts, packages/canvas/src/__tests__/*, packages/views/src/__tests__/*
Adds unit and integration tests for page-task collection, task metadata, smart references, useTasks, usePageTaskSync, plus CI-aware test timing helpers.

Sequence Diagram

sequenceDiagram
    participant User
    participant Editor as RichTextEditor
    participant Ext as PageTaskExtension
    participant Hook as usePageTaskSync
    participant API as Task/ExternalReference API

    User->>Editor: edit task content (title, `@mention`, due date, embed)
    Editor->>Ext: collectPageTasks() -> snapshot + attribute updates
    Ext-->>Editor: snapshot (and optional tx updates)
    Editor->>Hook: onPageTasksChange(snapshot)
    Note over Hook: debounced aggregation
    Hook->>API: create/update/restore/remove Tasks & upsert ExternalReferences
    API-->>Hook: responses
    Hook-->>Editor: sync status / error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through nodes and sprinkled IDs,

Mentions, dates, embeds — tidy little grids,
Pages whisper tasks to the canonical hive,
Panels bloom, previews fly, the site comes alive,
A joyful rabbit syncs the hive to thrive!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.66% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat(tasks): add inline assignees and due dates to page tasks' directly reflects the core additions: introducing inline @mention assignee chips and inline due-date chips for page-backed tasks in the editor, plus syncing and persisting assignee DIDs and due dates into canonical Task nodes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/tasks-page-task-sync

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

@crs48 crs48 added the enhancement New feature or request label Mar 6, 2026
@greptile-apps

greptile-apps Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces inline @mention assignee chips and due-date chips for page-backed task rows in the rich-text editor, and wires them through a new usePageTaskSync hook that reconciles ProseMirror task snapshots with canonical Task nodes in the data layer. It also adds ExternalReference schema support, a TaskViewEmbed block node for embedded filtered task views, a SmartReferenceExtension that auto-converts pasted URLs inside task items into structured chips, and shared PageTasksPanel / TaskCollectionEmbed panel components consumed by both the web and Electron apps.

Key points:

  • Timezone display inconsistency in panel components: formatDueDate in PageTasksPanel.tsx and TaskCollectionEmbed.tsx uses new Date(timestamp).toLocaleDateString() to render due-date labels. Because due-date timestamps are stored as UTC midnight (correctly, via Date.UTC in usePageTaskSync), users in UTC− timezones see the date shifted one day earlier (e.g. "Mar 19" instead of "Mar 20"). The inline chip rendered by TaskDueDateExtension works from the date string directly and is unaffected, creating a visible mismatch between the chip and the panel. Pinning the formatter to timeZone: 'UTC' fixes this.
  • Local user stub blocks display-name resolution in buildTaskMentionSuggestions: The current user is added to the suggestion map first as a bare { did } stub (no name/avatar). The suggestions.has(did) guard then prevents a richer presence entry — carrying the actual display name and avatar — from ever being applied. The @mention chip for the current user always shows a truncated DID label.
  • Several issues identified in earlier review rounds remain open: arraysEqual order-sensitivity, getTodayDateString using local time for the overdue indicator, the toolbar's inability to clear an existing due date, and the interface vs type style-guide violations.
  • Test coverage for the new hooks is thorough and integration-tested against a real in-memory storage adapter.

Confidence Score: 3/5

  • Mergeable after addressing the timezone display bug and local-user name suppression; remaining open threads from prior rounds should also be resolved before landing.
  • The feature is well-architected and comprehensively tested, but there are two new logic bugs (formatDueDate timezone shift, mention name suppression) on top of multiple carry-over issues from previous review rounds that have not yet been addressed (arraysEqual order-sensitivity, getTodayDateString local-time overdue indicator, toolbar clear-date affordance). None are catastrophic, but their cumulative effect means the feature behaves incorrectly for users in non-UTC timezones and degrades the @mention UX.
  • packages/react/src/components/PageTasksPanel.tsx, packages/react/src/components/TaskCollectionEmbed.tsx (formatDueDate timezone), and packages/editor/src/utils/taskMentionSuggestions.ts (local-user name suppression).

Important Files Changed

Filename Overview
packages/react/src/hooks/usePageTaskSync.ts Core reconciliation hook: syncs ProseMirror task snapshots to canonical Task nodes. References are now correctly upserted before tasks. Several issues previously flagged (ordering, timezone storage, arraysEqual order-sensitivity, interface vs type).
packages/react/src/components/TaskCollectionEmbed.tsx Embedded task view component. formatDueDate uses toLocaleDateString which shifts dates for users west of UTC (new bug). getStartOfUtcDay is duplicated from useTasks.ts. The assignee === 'me' + null DID issue was previously flagged.
packages/react/src/components/PageTasksPanel.tsx Page-scoped task sidebar panel. Same formatDueDate timezone display bug as TaskCollectionEmbed. Also has a duplicate getStartOfUtcDay definition.
packages/editor/src/extensions/page-tasks/index.ts ProseMirror task extraction and stable-ID assignment. Position arithmetic correctly accounts for doc vs non-doc node offset differences. Sort keys, assignee/due-date/reference extraction look correct.
packages/editor/src/extensions/task-metadata/TaskDueDateExtension.ts Inline due-date chip extension. getTodayDateString uses local time for the overdue indicator while the data layer uses UTC midnight (previously flagged). clearTaskDueDate exists but the toolbar only calls setTaskDueDate (toolbar "clear" issue previously flagged).
packages/editor/src/utils/taskMentionSuggestions.ts Builds @mention suggestion list. The local user stub is added before iterating presence, so a richer presence entry (with display name/avatar) for that user is silently discarded — a new logic bug flagged in this review.
packages/editor/src/components/FloatingToolbar.tsx Toolbar gains task-context buttons (@mention, due date). The pickDate blur-before-change race was previously flagged. The toolbar cannot clear an existing due date — also previously flagged.

Sequence Diagram

sequenceDiagram
    participant E as RichTextEditor
    participant PT as PageTaskItemExtension
    participant RTM as TaskMentionExtension
    participant RTD as TaskDueDateExtension
    participant SR as SmartReferenceExtension
    participant PTS as usePageTaskSync
    participant Q as useQuery / useMutate
    participant DB as NodeStorage

    E->>PT: editor 'update' event
    PT->>E: collectPageTasks(doc) → PageTaskSnapshot[]
    E->>PTS: onPageTasksChange(snapshots)

    Note over PTS: debounce 250 ms

    PTS->>Q: useQuery(TaskSchema, { where: { page } })
    Q-->>PTS: existingTasks[]

    PTS->>PTS: diff snapshots vs existingTasks

    PTS->>DB: upsert ExternalReference nodes (refs first)
    PTS->>DB: restore soft-deleted tasks
    PTS->>DB: create new Task nodes
    PTS->>DB: update changed Task nodes
    PTS->>DB: soft-delete removed tasks

    DB-->>Q: reactive update
    Q-->>E: TaskCollectionEmbed / PageTasksPanel re-renders

    Note over E,RTM: @mention flow
    E->>RTM: user types '@'
    RTM->>E: getSuggestions() from mentionSuggestionsRef
    RTM-->>E: TaskMentionMenu dropdown

    Note over E,SR: paste flow
    E->>SR: handlePaste (inside taskItem)
    SR->>SR: parseSmartReferenceUrl(text)
    SR-->>E: insert smartReference chip
Loading

Last reviewed commit: 9f55dd5

Comment thread packages/react/src/hooks/usePageTaskSync.ts
Comment thread packages/react/src/hooks/usePageTaskSync.ts Outdated
Comment on lines +58 to +67
function arraysEqual(a: string[] | undefined, b: string[]): boolean {
if (!Array.isArray(a)) return b.length === 0
if (a.length !== b.length) return false

for (let index = 0; index < a.length; index += 1) {
if (a[index] !== b[index]) return false
}

return true
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

arraysEqual is order-sensitive, triggering spurious updates when assignee order differs

normalizeAssignees uses Set (insertion-order, not sorted), so ['did:a', 'did:b'] and ['did:b', 'did:a'] produce two different arrays. If the editor snapshot delivers assignees in a different order than was previously stored, arraysEqual returns false and an update is written even though the set of assignees hasn't changed. Over time — especially with multiple concurrent editors — this could produce high-frequency unnecessary writes.

Sorting before comparison (or sorting inside normalizeAssignees) would make this stable:

Suggested change
function arraysEqual(a: string[] | undefined, b: string[]): boolean {
if (!Array.isArray(a)) return b.length === 0
if (a.length !== b.length) return false
for (let index = 0; index < a.length; index += 1) {
if (a[index] !== b[index]) return false
}
return true
}
function arraysEqual(a: string[] | undefined, b: string[]): boolean {
if (!Array.isArray(a)) return b.length === 0
if (a.length !== b.length) return false
const sortedA = [...a].sort()
const sortedB = [...b].sort()
for (let index = 0; index < sortedA.length; index += 1) {
if (sortedA[index] !== sortedB[index]) return false
}
return true
}

- switch production site publishing from artifact-based Pages deploys to a gh-pages branch

- add pull request preview publishing under pr/<number>/app with PR comments

- preserve production content while updating previews and clean preview folders on PR close

- align the manual undeploy workflow with the new branch-backed Pages model
crs48 pushed a commit that referenced this pull request Mar 6, 2026
- publish the current production site tree to gh-pages

- publish PR #4 web preview under pr/4/app

- add the static files needed for custom-domain Pages hosting

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (12)
packages/editor/src/components/TaskMentionMenu.tsx (2)

4-18: Consider using type instead of interface for object shapes.

As per coding guidelines, prefer type over interface for defining object shapes in TypeScript.

♻️ Suggested refactor
-export interface TaskMentionSuggestion {
+export type TaskMentionSuggestion = {
   id: string
   label: string
   subtitle?: string
   color?: string
 }

-interface TaskMentionMenuProps {
+type TaskMentionMenuProps = {
   items: TaskMentionSuggestion[]
   command: (item: TaskMentionSuggestion) => void
 }

-export interface TaskMentionMenuRef {
+export type TaskMentionMenuRef = {
   onKeyDown: (event: KeyboardEvent) => boolean
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/components/TaskMentionMenu.tsx` around lines 4 - 18,
Replace the exported and local interfaces with type aliases: change
TaskMentionSuggestion, TaskMentionMenuProps, and TaskMentionMenuRef from
interface declarations to equivalent type declarations so the object shapes use
type instead of interface; ensure the exported name TaskMentionSuggestion
remains exported and that the signatures for command and onKeyDown keep the same
types.

1-3: Consider adding file-level JSDoc.

As per coding guidelines for packages/**/*.{ts,tsx}, include a file-level JSDoc at the top of the file.

📝 Suggested fix
+/**
+ * `@xnetjs/editor` - Task mention menu component
+ *
+ * Provides a keyboard-navigable dropdown for selecting assignees in task mentions.
+ */
 import { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from 'react'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/components/TaskMentionMenu.tsx` around lines 1 - 3, Add a
file-level JSDoc comment at the top of TaskMentionMenu.tsx describing the
module's purpose and exported component(s) (e.g., that this file implements the
TaskMentionMenu React component used for inserting task mentions), including any
relevant tags like `@module` and a brief summary; place the JSDoc immediately
above the existing imports so it applies to the whole file and keep it concise
per the repository guidelines.
docs/explorations/0103_[_]_TASKS_EMBEDDED_IN_PAGES_BACKED_BY_NODES_MENTIONS_DUE_DATES_NESTED_SUBTASKS_DATABASES_CANVASES_AND_CROSS_SURFACE_TASK_MODEL.md (1)

220-220: Minor: Fix space in code span.

The markdown linter flagged spaces inside the code span. Consider removing the spaces around [].

📝 Suggested fix
-- type `[] `,
+- type `[]`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@docs/explorations/0103_`[_]_TASKS_EMBEDDED_IN_PAGES_BACKED_BY_NODES_MENTIONS_DUE_DATES_NESTED_SUBTASKS_DATABASES_CANVASES_AND_CROSS_SURFACE_TASK_MODEL.md
at line 220, The markdown has an extra space inside the code span around the
square brackets — update the span containing `[] ` so it has no internal
trailing/leading spaces (change `[] ` to `[]`) where the fragment reads "type
`[] `," to satisfy the linter; search for the code span `[] ` and remove the
space so it becomes `[]`.
apps/electron/src/renderer/components/PageView.tsx (1)

105-129: Consider using for...of to avoid lint warning on forEach callback.

The addSuggestion function has an early return (line 115) which causes the Biome lint warning about returning a value from a forEach callback. While the logic is correct, using for...of is cleaner and avoids the warning.

♻️ Suggested refactor
-    addSuggestion(did ? { did } : null, true)
-    presence.forEach((user) => addSuggestion(user))
+    addSuggestion(did ? { did } : null, true)
+    for (const user of presence) {
+      addSuggestion(user)
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/PageView.tsx` around lines 105 - 129,
The Biome lint warning comes from using presence.forEach with addSuggestion
which has an early return; change the iteration to a for...of loop to avoid
returning from a forEach callback. In the memo block around mentionSuggestions,
replace the presence.forEach(...) call with a simple for (const user of
presence) call that invokes addSuggestion(user) (keep addSuggestion, presence,
did and mentionSuggestions names intact) so behavior is unchanged but the lint
warning is eliminated.
packages/editor/src/extensions/smart-reference/SmartReferenceExtension.test.ts (1)

1-1: Add the required file-level JSDoc header.

Line 1 starts with imports; this new package test file should include a top-level JSDoc banner per repo standard.

As per coding guidelines, packages/**/*.{ts,tsx}: Include file-level JSDoc at top of file: /** @xnetjs/sync - Unified sync primitives for xNet */.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/editor/src/extensions/smart-reference/SmartReferenceExtension.test.ts`
at line 1, Add the required file-level JSDoc banner at the very top of the test
file before any imports; insert the exact header /** `@xnetjs/sync` - Unified sync
primitives for xNet */ above the existing import statement (the file currently
imports Editor from '@tiptap/core') so the file complies with the
packages/**/*.{ts,tsx} guideline and the package-level standard.
packages/react/src/hooks/usePageTaskSync.test.tsx (1)

1-1: Add the required file-level JSDoc header.

Line 1 starts immediately with imports; this new packages/* file should include the repository’s file-level JSDoc banner.

As per coding guidelines, packages/**/*.{ts,tsx}: Include file-level JSDoc at top of file: /** @xnetjs/sync - Unified sync primitives for xNet */.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/hooks/usePageTaskSync.test.tsx` at line 1, Add the
repository’s required file-level JSDoc banner at the very top of the file before
the first import (currently "import type { DID } from '@xnetjs/core'"); insert
the exact header /** `@xnetjs/sync` - Unified sync primitives for xNet */ as the
first line so the file complies with the packages/**/*.{ts,tsx} guideline.
packages/editor/src/extensions/task-metadata/TaskDueDateExtension.ts (1)

1-4: Add package header JSDoc and prefer type for options shape.

As per coding guidelines, "Include file-level JSDoc at top of file: /** @xnetjs/sync - Unified sync primitives for xNet */" and "Prefer type over interface for defining object shapes in TypeScript".

Also applies to: 102-104

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/extensions/task-metadata/TaskDueDateExtension.ts` around
lines 1 - 4, Add the required file-level JSDoc header at the top of
TaskDueDateExtension.ts (e.g. /** `@xnetjs/sync` - Unified sync primitives for
xNet */) and replace any exported interface used for options (e.g.
TaskDueDateOptions or similar options interface/type declarations) with a
TypeScript `type` alias instead of `interface`; update any references to that
symbol (constructor signatures, defaultOptions, getOptions, or extension
declaration) to use the new type name so the file follows the coding guideline
preferring `type` for object shapes.
packages/editor/src/extensions/smart-reference/SmartReferenceExtension.ts (1)

11-47: Align declarations with naming/type guidelines (SCREAMING_SNAKE_CASE + type).

Rename SmartReferencePastePluginKey to SCREAMING_SNAKE_CASE and convert SmartReferenceOptions to a type alias.

As per coding guidelines, "Constants must use SCREAMING_SNAKE_CASE: MAX_UPDATE_SIZE, DEFAULT_TTL" and "Prefer type over interface for defining object shapes in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/extensions/smart-reference/SmartReferenceExtension.ts`
around lines 11 - 47, Rename the PluginKey constant SmartReferencePastePluginKey
to SCREAMING_SNAKE_CASE (e.g., SMART_REFERENCE_PASTE_PLUGIN_KEY) and update
every reference to that identifier (including where new
PluginKey('smartReferencePaste') is created and any uses/imports/exports). Also
convert the exported interface SmartReferenceOptions into a type alias (e.g.,
type SmartReferenceOptions = { autoConvertTaskPaste: boolean; HTMLAttributes:
Record<string, string>; }) and update any places that reference the interface to
use the new type alias. Ensure exports and any imports elsewhere are updated to
the new names to keep types and runtime identifiers consistent.
packages/editor/src/extensions/smart-reference/providers.ts (1)

21-31: Use a type alias for SmartReference object shape.

As per coding guidelines, "Prefer type over interface for defining object shapes in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/extensions/smart-reference/providers.ts` around lines 21
- 31, Replace the exported interface SmartReference with a type alias that
preserves the exact object shape; change the declaration from "export interface
SmartReference { ... }" to "export type SmartReference = { ... }" keeping all
fields (provider, kind: SmartReferenceKind, url, refId, title, subtitle?, icon,
embedUrl?, metadata: Record<string,string>) and export, and then run a quick
grep for any places that rely on interface-specific features (like declaration
merging or "implements") and adjust those usages if necessary.
packages/editor/src/extensions/page-tasks/index.ts (1)

1-27: Add package JSDoc header and switch snapshot shapes to type aliases.

As per coding guidelines, "Include file-level JSDoc at top of file: /** @xnetjs/sync - Unified sync primitives for xNet */" and "Prefer type over interface for defining object shapes in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/extensions/page-tasks/index.ts` around lines 1 - 27, Add
the required file-level JSDoc header exactly as /** `@xnetjs/sync` - Unified sync
primitives for xNet */ at the very top of the file, and convert the exported
interfaces PageTaskReferenceSnapshot and PageTaskSnapshot into type aliases with
identical object shapes (replace "export interface PageTaskReferenceSnapshot {
... }" and "export interface PageTaskSnapshot { ... }" with "export type
PageTaskReferenceSnapshot = { ... }" and "export type PageTaskSnapshot = { ...
}" respectively), keeping all property names and types unchanged.
packages/editor/src/components/RichTextEditor.tsx (1)

4-44: Reorder imports to keep external imports before internal/local imports.

As per coding guidelines, "Internal package imports should follow external packages: import { hash } from '@xnetjs/crypto'" and "Local relative imports should come last: import { helper } from './utils'".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/components/RichTextEditor.tsx` around lines 4 - 44, Move
all external/package imports to the top and place the local/internal imports
(everything imported from '../extensions') after them: keep imports from
'@tiptap/*', 'y-protocols/awareness', 'yjs', 'react', etc. at the top, then
import the types and symbols coming from '../extensions' (DatabaseViewType,
PageTaskSnapshot, SlashCommandItem, TaskMentionSuggestion, Wikilink,
LivePreview, HeadingWithSyntax, CodeBlockWithSyntax, BlockquoteWithSyntax,
SlashCommand, DragHandleExtension, KeyboardShortcutsExtension, ImageExtension,
CalloutExtension, ToggleExtension, FileExtension, SmartReferenceExtension,
EmbedExtension, DatabaseEmbedExtension, PageTaskItemExtension,
TaskMentionExtension, TaskDueDateExtension, ensurePageTaskAttrs,
getPageTasksSnapshot) after those external imports so local relative imports are
last and import ordering follows the project's guideline.
packages/editor/src/extensions/task-metadata/TaskMentionExtension.ts (1)

1-40: Apply guideline conformance for new task-mention module declarations.

Please add the package-level JSDoc banner, rename TaskMentionSuggestionPluginKey to SCREAMING_SNAKE_CASE, and convert TaskMentionOptions to a type alias.

As per coding guidelines, "Include file-level JSDoc at top of file: /** @xnetjs/sync - Unified sync primitives for xNet */", "Constants must use SCREAMING_SNAKE_CASE: MAX_UPDATE_SIZE, DEFAULT_TTL", and "Prefer type over interface for defining object shapes in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/extensions/task-metadata/TaskMentionExtension.ts` around
lines 1 - 40, Add the required file-level JSDoc banner at the very top of the
file (use /** `@xnetjs/sync` - Unified sync primitives for xNet */), rename the
PluginKey constant TaskMentionSuggestionPluginKey to a SCREAMING_SNAKE_CASE
identifier (e.g., TASK_MENTION_SUGGESTION_PLUGIN_KEY) and update all references,
and convert the exported TaskMentionOptions interface into an equivalent type
alias named TaskMentionOptions while preserving its shape (getSuggestions and
HTMLAttributes) and exports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/routes/doc`.$docId.tsx:
- Line 101: The forEach callback on presence uses an expression-bodied arrow
which triggers the lint rule; change the callback to a block-bodied function so
the return value is explicit: locate the call presence.forEach((user) =>
addSuggestion(user)) and replace it with a block-bodied callback that calls
addSuggestion(user) inside a { ... } body (i.e., presence.forEach((user) => {
addSuggestion(user); })), keeping the same behavior and referencing
addSuggestion and presence.

In `@docs/explorations/0102_`[_]_AFFINE_BLOCKSUITE_INTEGRATION_FEASIBILITY.md:
- Line 810: The table entries claiming "BlockSuite is MPL 2.0 (permissive)" and
"MPL 2.0 → MIT is compatible" are inaccurate; update those cells (the row titled
"Legal issues - License or trademark concerns" and the two text snippets) to
either (a) replace the first with wording that MPL-2.0 is a weak copyleft
license requiring modified MPL-covered files distributed to remain under MPL-2.0
and source for those files be made available to recipients, and (b) replace the
second with wording that MIT and MPL-2.0 can be combined in a Larger Work but
MPL-covered files cannot be relicensed to MIT (or alternatively add "requires
legal review for specific distribution scenario" if you prefer a cautionary
note); ensure the revised text explicitly mentions the file-level obligations
and the correct compatibility constraint.

In `@packages/editor/src/components/FloatingToolbar.tsx`:
- Around line 119-123: Wrap the call to input.showPicker() inside a try-catch in
the handlePickDueDate flow: if typeof input.showPicker === 'function' attempt
await input.showPicker() inside try, on any caught error call input.click() as a
fallback, and ensure a final safety net by calling finish(null) in a finally
block (or after catch) so the promise cannot hang; retain the existing
input.click() branch for non-supporting browsers.

In `@packages/editor/src/components/RichTextEditor.tsx`:
- Around line 495-497: The initial call to publishPageTasks() happens before the
update listener is registered, so synchronous attribute updates from
ensurePageTaskAttrs(editor) trigger an 'update' event that is missed; swap the
order so you register the listener first by calling editor.on('update',
publishPageTasks) before invoking publishPageTasks(), ensuring publishPageTasks,
ensurePageTaskAttrs(editor) and the onPageTasksChange flow run with the listener
in place.

In `@packages/editor/src/components/TaskMentionMenu.tsx`:
- Around line 38-60: The onKeyDown handler exposed via useImperativeHandle must
guard against an empty items array to avoid NaN from modulo by zero: inside the
onKeyDown implementation (the function returned by useImperativeHandle that
handles 'ArrowUp', 'ArrowDown', and 'Enter'), check if items.length === 0 and
return false (or ignore keys) before performing modulo operations or calling
setSelectedIndex/selectItem; update the branches handling 'ArrowUp' and
'ArrowDown' to early-return when items is empty and ensure the 'Enter' branch
also checks items.length>0 before calling selectItem(selectedIndex).

In `@packages/editor/src/extensions/task-metadata/TaskDueDateExtension.ts`:
- Around line 163-176: The setTaskDueDate command currently creates a
taskDueDate node even when the selection is not inside a taskItem; update the
command (the ({ editor, state, dispatch }) => { ... } handler that calls
normalizeDateString, create on this.type, and uses
findCurrentTaskItem/findDueDateNode) to early-return false if
findCurrentTaskItem(editor) is null (i.e., cursor not inside a taskItem) before
creating or inserting nodes; also add a file-level JSDoc header at the top of
TaskDueDateExtension.ts and change the TaskDueDateOptions declaration from an
interface to a type alias to match project style.

In `@packages/react/src/hooks/usePageTaskSync.ts`:
- Around line 194-213: During mapping in usePageTaskSync, don't populate
referenceUpserts unconditionally; instead build a temporary nextReferences array
of ids while computing ids with computeExternalReferenceId and collecting the
reference objects, then after change detection using
arraysEqual(existingTask.references, referenceIds) (or the inverse check), only
transfer the collected upserts into referenceUpserts when references actually
differ; update the code paths that currently push into referenceUpserts inside
the map to push into a local collector and perform the push to referenceUpserts
after the no-op check.
- Around line 171-345: Introduce a run-id check to avoid stale DB mutations: at
the start of run() capture a local runId by incrementing/reading
syncRunIdRef.current (create syncRunIdRef if missing), and before each database
operation loop (the restore loop, tasksToCreate loop using
create(TaskSchema,...), taskUpdates loop using update(TaskSchema,...),
taskDeletes loop using remove(...), and referenceUpserts loop using
update/create on ExternalReferenceSchema) verify if cancelled || runId !==
syncRunIdRef.current and return early if true; keep existing cancelled checks
around setSyncing/setError but ensure DB operations bail when stale so stale
mutations cannot execute.
- Around line 106-112: The toDateTimestamp function uses local-time Date
construction which can auto-roll invalid calendar dates and yield
timezone-dependent results; update toDateTimestamp to parse the YYYY-MM-DD
parts, construct a UTC timestamp via Date.UTC(year, month-1, day), then verify
the constructed UTC date components match the parsed year/month/day to reject
overflow values (e.g., 2026-02-31) and return undefined on mismatch or NaN,
ensuring identical UTC timestamps across environments.

---

Nitpick comments:
In `@apps/electron/src/renderer/components/PageView.tsx`:
- Around line 105-129: The Biome lint warning comes from using presence.forEach
with addSuggestion which has an early return; change the iteration to a for...of
loop to avoid returning from a forEach callback. In the memo block around
mentionSuggestions, replace the presence.forEach(...) call with a simple for
(const user of presence) call that invokes addSuggestion(user) (keep
addSuggestion, presence, did and mentionSuggestions names intact) so behavior is
unchanged but the lint warning is eliminated.

In
`@docs/explorations/0103_`[_]_TASKS_EMBEDDED_IN_PAGES_BACKED_BY_NODES_MENTIONS_DUE_DATES_NESTED_SUBTASKS_DATABASES_CANVASES_AND_CROSS_SURFACE_TASK_MODEL.md:
- Line 220: The markdown has an extra space inside the code span around the
square brackets — update the span containing `[] ` so it has no internal
trailing/leading spaces (change `[] ` to `[]`) where the fragment reads "type
`[] `," to satisfy the linter; search for the code span `[] ` and remove the
space so it becomes `[]`.

In `@packages/editor/src/components/RichTextEditor.tsx`:
- Around line 4-44: Move all external/package imports to the top and place the
local/internal imports (everything imported from '../extensions') after them:
keep imports from '@tiptap/*', 'y-protocols/awareness', 'yjs', 'react', etc. at
the top, then import the types and symbols coming from '../extensions'
(DatabaseViewType, PageTaskSnapshot, SlashCommandItem, TaskMentionSuggestion,
Wikilink, LivePreview, HeadingWithSyntax, CodeBlockWithSyntax,
BlockquoteWithSyntax, SlashCommand, DragHandleExtension,
KeyboardShortcutsExtension, ImageExtension, CalloutExtension, ToggleExtension,
FileExtension, SmartReferenceExtension, EmbedExtension, DatabaseEmbedExtension,
PageTaskItemExtension, TaskMentionExtension, TaskDueDateExtension,
ensurePageTaskAttrs, getPageTasksSnapshot) after those external imports so local
relative imports are last and import ordering follows the project's guideline.

In `@packages/editor/src/components/TaskMentionMenu.tsx`:
- Around line 4-18: Replace the exported and local interfaces with type aliases:
change TaskMentionSuggestion, TaskMentionMenuProps, and TaskMentionMenuRef from
interface declarations to equivalent type declarations so the object shapes use
type instead of interface; ensure the exported name TaskMentionSuggestion
remains exported and that the signatures for command and onKeyDown keep the same
types.
- Around line 1-3: Add a file-level JSDoc comment at the top of
TaskMentionMenu.tsx describing the module's purpose and exported component(s)
(e.g., that this file implements the TaskMentionMenu React component used for
inserting task mentions), including any relevant tags like `@module` and a brief
summary; place the JSDoc immediately above the existing imports so it applies to
the whole file and keep it concise per the repository guidelines.

In `@packages/editor/src/extensions/page-tasks/index.ts`:
- Around line 1-27: Add the required file-level JSDoc header exactly as /**
`@xnetjs/sync` - Unified sync primitives for xNet */ at the very top of the file,
and convert the exported interfaces PageTaskReferenceSnapshot and
PageTaskSnapshot into type aliases with identical object shapes (replace "export
interface PageTaskReferenceSnapshot { ... }" and "export interface
PageTaskSnapshot { ... }" with "export type PageTaskReferenceSnapshot = { ... }"
and "export type PageTaskSnapshot = { ... }" respectively), keeping all property
names and types unchanged.

In `@packages/editor/src/extensions/smart-reference/providers.ts`:
- Around line 21-31: Replace the exported interface SmartReference with a type
alias that preserves the exact object shape; change the declaration from "export
interface SmartReference { ... }" to "export type SmartReference = { ... }"
keeping all fields (provider, kind: SmartReferenceKind, url, refId, title,
subtitle?, icon, embedUrl?, metadata: Record<string,string>) and export, and
then run a quick grep for any places that rely on interface-specific features
(like declaration merging or "implements") and adjust those usages if necessary.

In
`@packages/editor/src/extensions/smart-reference/SmartReferenceExtension.test.ts`:
- Line 1: Add the required file-level JSDoc banner at the very top of the test
file before any imports; insert the exact header /** `@xnetjs/sync` - Unified sync
primitives for xNet */ above the existing import statement (the file currently
imports Editor from '@tiptap/core') so the file complies with the
packages/**/*.{ts,tsx} guideline and the package-level standard.

In `@packages/editor/src/extensions/smart-reference/SmartReferenceExtension.ts`:
- Around line 11-47: Rename the PluginKey constant SmartReferencePastePluginKey
to SCREAMING_SNAKE_CASE (e.g., SMART_REFERENCE_PASTE_PLUGIN_KEY) and update
every reference to that identifier (including where new
PluginKey('smartReferencePaste') is created and any uses/imports/exports). Also
convert the exported interface SmartReferenceOptions into a type alias (e.g.,
type SmartReferenceOptions = { autoConvertTaskPaste: boolean; HTMLAttributes:
Record<string, string>; }) and update any places that reference the interface to
use the new type alias. Ensure exports and any imports elsewhere are updated to
the new names to keep types and runtime identifiers consistent.

In `@packages/editor/src/extensions/task-metadata/TaskDueDateExtension.ts`:
- Around line 1-4: Add the required file-level JSDoc header at the top of
TaskDueDateExtension.ts (e.g. /** `@xnetjs/sync` - Unified sync primitives for
xNet */) and replace any exported interface used for options (e.g.
TaskDueDateOptions or similar options interface/type declarations) with a
TypeScript `type` alias instead of `interface`; update any references to that
symbol (constructor signatures, defaultOptions, getOptions, or extension
declaration) to use the new type name so the file follows the coding guideline
preferring `type` for object shapes.

In `@packages/editor/src/extensions/task-metadata/TaskMentionExtension.ts`:
- Around line 1-40: Add the required file-level JSDoc banner at the very top of
the file (use /** `@xnetjs/sync` - Unified sync primitives for xNet */), rename
the PluginKey constant TaskMentionSuggestionPluginKey to a SCREAMING_SNAKE_CASE
identifier (e.g., TASK_MENTION_SUGGESTION_PLUGIN_KEY) and update all references,
and convert the exported TaskMentionOptions interface into an equivalent type
alias named TaskMentionOptions while preserving its shape (getSuggestions and
HTMLAttributes) and exports.

In `@packages/react/src/hooks/usePageTaskSync.test.tsx`:
- Line 1: Add the repository’s required file-level JSDoc banner at the very top
of the file before the first import (currently "import type { DID } from
'@xnetjs/core'"); insert the exact header /** `@xnetjs/sync` - Unified sync
primitives for xNet */ as the first line so the file complies with the
packages/**/*.{ts,tsx} guideline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 55e28434-55e2-4538-9e3c-03054525c325

📥 Commits

Reviewing files that changed from the base of the PR and between 9a412ec and 3e168d9.

📒 Files selected for processing (34)
  • apps/electron/src/renderer/components/PageView.tsx
  • apps/web/src/components/Editor.tsx
  • apps/web/src/routes/doc.$docId.tsx
  • docs/explorations/0102_[_]_AFFINE_BLOCKSUITE_INTEGRATION_FEASIBILITY.md
  • docs/explorations/0103_[_]_TASKS_EMBEDDED_IN_PAGES_BACKED_BY_NODES_MENTIONS_DUE_DATES_NESTED_SUBTASKS_DATABASES_CANVASES_AND_CROSS_SURFACE_TASK_MODEL.md
  • packages/data/src/index.ts
  • packages/data/src/schema/index.ts
  • packages/data/src/schema/schemas/external-reference.test.ts
  • packages/data/src/schema/schemas/external-reference.ts
  • packages/data/src/schema/schemas/index.ts
  • packages/data/src/schema/schemas/task.test.ts
  • packages/data/src/schema/schemas/task.ts
  • packages/editor/src/components/FloatingToolbar.tsx
  • packages/editor/src/components/RichTextEditor.tsx
  • packages/editor/src/components/TaskMentionMenu.tsx
  • packages/editor/src/components/editor-ux-state.ts
  • packages/editor/src/extensions.ts
  • packages/editor/src/extensions/page-tasks/PageTaskItemExtension.test.ts
  • packages/editor/src/extensions/page-tasks/index.ts
  • packages/editor/src/extensions/smart-reference/SmartReferenceExtension.test.ts
  • packages/editor/src/extensions/smart-reference/SmartReferenceExtension.ts
  • packages/editor/src/extensions/smart-reference/index.ts
  • packages/editor/src/extensions/smart-reference/providers.test.ts
  • packages/editor/src/extensions/smart-reference/providers.ts
  • packages/editor/src/extensions/task-metadata/TaskDueDateExtension.ts
  • packages/editor/src/extensions/task-metadata/TaskMentionExtension.ts
  • packages/editor/src/extensions/task-metadata/TaskMetadataExtension.test.ts
  • packages/editor/src/extensions/task-metadata/index.ts
  • packages/editor/src/react.ts
  • packages/editor/src/styles/editor.css
  • packages/react/src/hooks/usePageTaskSync.test.tsx
  • packages/react/src/hooks/usePageTaskSync.ts
  • packages/react/src/index.ts
  • site/src/layouts/Base.astro

Comment thread apps/web/src/routes/doc.$docId.tsx Outdated
Comment thread docs/explorations/0102_[_]_AFFINE_BLOCKSUITE_INTEGRATION_FEASIBILITY.md Outdated
Comment on lines +119 to +123
if (typeof input.showPicker === 'function') {
input.showPicker()
} else {
input.click()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

wc -l packages/editor/src/components/FloatingToolbar.tsx

Repository: crs48/xNet

Length of output: 108


🏁 Script executed:

sed -n '100,150p' packages/editor/src/components/FloatingToolbar.tsx

Repository: crs48/xNet

Length of output: 1161


🏁 Script executed:

sed -n '1,30p' packages/editor/src/components/FloatingToolbar.tsx

Repository: crs48/xNet

Length of output: 1026


🏁 Script executed:

sed -n '80,130p' packages/editor/src/components/FloatingToolbar.tsx

Repository: crs48/xNet

Length of output: 1127


🏁 Script executed:

sed -n '60,120p' packages/editor/src/components/FloatingToolbar.tsx

Repository: crs48/xNet

Length of output: 1492


🌐 Web query:

HTMLInputElement showPicker() throws error browser compatibility

💡 Result:

HTMLInputElement.showPicker() errors usually come from one of these causes:

  1. Browser doesn’t support it (or is too old)
    If you see TypeError: input.showPicker is not a function, that browser/version doesn’t implement it. Support starts around Chrome/Edge 99+, Firefox 101+, Safari 16+ (macOS + iOS). Check the exact versions your users have. [2]

  2. Not triggered by a user gesture
    If you call it from setTimeout, Promise.then, page load, etc., browsers can throw NotAllowedError because it requires transient user activation (e.g., inside a click/tap handler). [1] [3]

  3. Cross-origin iframe restrictions
    If the input is inside a cross-origin <iframe>, it can throw SecurityError (with exceptions for type="file" and type="color"). [1] [3]

  4. Input is not “mutable”
    If the input is not user-editable (commonly disabled, and in some cases other states), it may throw InvalidStateError. [1] [3]

  5. Safari/iOS reality check (partial/buggy behavior)
    Even where “supported”, there are real-world Safari/iOS limitations/bugs—e.g., reports that on iOS/iPadOS it may effectively work only for certain picker types, and picker-closing behavior can be problematic. Treat it as progressive enhancement. [4]

Recommended safe pattern

button.addEventListener("click", () => {
  const input = document.querySelector('input[type="date"]');

  if (typeof input?.showPicker === "function") {
    try {
      input.showPicker();          // must be in this user-gesture handler
    } catch (e) {
      input.focus();               // fallback (may open picker on some mobile browsers)
    }
  } else {
    input.focus();                 // fallback for unsupported browsers
  }
});

If your “error” is TypeScript (“Property 'showPicker' does not exist…”)

That’s a TypeScript DOM typings version issue: update TypeScript (the DOM lib typings come from TS), or temporarily cast: (input as any).showPicker?.(). [5]


🌐 Web query:

showPicker() NotAllowedError NotSupportedError browser states

💡 Result:

showPicker() can fail with different DOMException names depending on state/context:

  • NotAllowedError: thrown when the call is not made with transient user activation (i.e., not directly in a trusted user gesture like a click/tap/keydown handler). This commonly happens after an await, inside setTimeout, promise callbacks, or message handlers where activation is no longer “transient.” [1][2]

  • NotSupportedError: (per the HTML Standard) applies to HTMLSelectElement.showPicker() when the <select> is not being rendered (e.g., not rendered due to CSS/layout). This is not the general “browser doesn’t support showPicker” case. [1]

Related (often confused with “unsupported”):

  • No exception; just does nothing: if an <input> doesn’t support a picker, HTMLInputElement.showPicker() is specified to do nothing (no NotSupportedError). [1]

  • InvalidStateError: if the element is not mutable (e.g., disabled / readonly in contexts where it makes it non-mutable). [1][2]

  • SecurityError: if called in a cross-origin iframe, except for type="file" and type="color" which are exempt. [1][2]

Sources:
[1] HTML Standard (showPicker() algorithm and exceptions) (html.spec.whatwg.org)
[2] MDN: HTMLInputElement.showPicker() exceptions/security notes (developer.mozilla.org)

Citations:


🏁 Script executed:

# Check if pickDate is called from event handlers to understand activation context
rg -A 3 "pickDate\(" packages/editor/src/components/FloatingToolbar.tsx

Repository: crs48/xNet

Length of output: 403


🏁 Script executed:

# Verify if there's any timeout/cleanup logic in the pickDate promise
sed -n '60,150p' packages/editor/src/components/FloatingToolbar.tsx | rg -A 5 "new Promise"

Repository: crs48/xNet

Length of output: 265


🏁 Script executed:

# Check if there's any timeout or promise rejection handling in pickDate or its callers
sed -n '220,260p' packages/editor/src/components/FloatingToolbar.tsx

Repository: crs48/xNet

Length of output: 1274


🏁 Script executed:

# Check if there's any error handling or timeout wrapper around the pickDate call
sed -n '230,250p' packages/editor/src/components/FloatingToolbar.tsx

Repository: crs48/xNet

Length of output: 662


Guard showPicker() with a try-catch to prevent promise hangs and add fallback behavior.

Lines 119-123 call showPicker() unguarded. This Web API can throw NotAllowedError, InvalidStateError, or SecurityError in various browser states/contexts. If an exception occurs, the promise never resolves because no event listeners catch it, causing handlePickDueDate to hang indefinitely. Add a try-catch wrapper with fallback to input.click() and a final finish(null) safety net.

🛠️ Suggested resilience patch
-    if (typeof input.showPicker === 'function') {
-      input.showPicker()
-    } else {
-      input.click()
-    }
+    try {
+      if (typeof input.showPicker === 'function') {
+        input.showPicker()
+      } else {
+        input.click()
+      }
+    } catch {
+      try {
+        input.click()
+      } catch {
+        finish(null)
+      }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (typeof input.showPicker === 'function') {
input.showPicker()
} else {
input.click()
}
try {
if (typeof input.showPicker === 'function') {
input.showPicker()
} else {
input.click()
}
} catch {
try {
input.click()
} catch {
finish(null)
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/components/FloatingToolbar.tsx` around lines 119 - 123,
Wrap the call to input.showPicker() inside a try-catch in the handlePickDueDate
flow: if typeof input.showPicker === 'function' attempt await input.showPicker()
inside try, on any caught error call input.click() as a fallback, and ensure a
final safety net by calling finish(null) in a finally block (or after catch) so
the promise cannot hang; retain the existing input.click() branch for
non-supporting browsers.

Comment thread packages/editor/src/components/RichTextEditor.tsx Outdated
Comment thread packages/editor/src/components/TaskMentionMenu.tsx
Comment thread packages/react/src/hooks/usePageTaskSync.ts
Comment thread packages/react/src/hooks/usePageTaskSync.ts
Comment thread packages/react/src/hooks/usePageTaskSync.ts
@crs48

crs48 commented Mar 6, 2026

Copy link
Copy Markdown
Owner Author

Preview: https://xnet.fyi/pr/4/app/

Comment thread packages/editor/src/components/FloatingToolbar.tsx
Comment on lines +17 to +19
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shared concurrency group cancels cross-workflow deployments

All four workflows (deploy-pr-preview.yml, deploy-site.yml, remove-pr-preview.yml, undeploy-site.yml) share group: pages-branch with cancel-in-progress: true. This means a push to main will cancel any in-flight PR preview deployment (and vice versa).

In the worst case: a PR preview run is mid-way through the rsync + git add steps when a main push triggers the site deploy and cancels it. The gh-pages worktree at /tmp/xnet-gh-pages is abandoned mid-edit, but since the push hadn't happened yet, the remote gh-pages branch is unaffected. The PR preview simply never gets published, with no error surfaced to the PR author.

A safer pattern is to use separate concurrency groups for PR previews and site deploys — they both operate on gh-pages but write to distinct subdirectories (/pr/<N>/ vs everything else):

# In deploy-pr-preview.yml / remove-pr-preview.yml
concurrency:
  group: pages-pr-${{ github.event.pull_request.number }}
  cancel-in-progress: true

# In deploy-site.yml / undeploy-site.yml
concurrency:
  group: pages-site
  cancel-in-progress: true

The rsync --exclude pr in deploy-site.yml and rsync -a --delete inside /pr/<N>/ in the preview workflows already keep them isolated at the file level, so separate groups would not introduce write conflicts.

This same issue is present in .github/workflows/remove-pr-preview.yml (line 17) and .github/workflows/undeploy-site.yml (line 12).

Comment on lines 12 to 14
workflow_dispatch:

permissions:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

contents: write is broader than the minimum required permission

The previous implementation used pages: write + id-token: write, which are scoped specifically to GitHub Pages deployments via the OIDC token exchange. The new approach replaces these with contents: write, which grants the workflow token write access to every branch and file in the repository.

For the site deploy (triggered by pushes to main), this is only exploitable by someone who can merge to main — acceptable for this repo. However, deploy-pr-preview.yml also uses contents: write, and it's triggered by pull_request events. Although the fork guard (if: github.event.pull_request.head.repo.full_name == github.repository) prevents external-fork PRs from running the job, any internal collaborator with PR-creation rights could run arbitrary workflow steps with write-to-all-branches access.

Consider splitting the token scope more tightly, or documenting the accepted risk:

permissions:
  contents: write  # Required to push to gh-pages branch only
  pull-requests: write  # Required to post preview link comments

GitHub doesn't yet offer a gh-pages: write fine-grained permission, so the comment-based documentation is the pragmatic mitigation here.

Comment on lines +13 to +46
export interface PageTaskReferenceInput {
url: string
provider: string | null
kind: string | null
refId: string | null
title: string | null
subtitle: string | null
icon: string | null
embedUrl: string | null
metadata: string
}

export interface PageTaskInput {
taskId: string
blockId: string
title: string
completed: boolean
parentTaskId: string | null
sortKey: string
assignees: string[]
dueDate: string | null
references: PageTaskReferenceInput[]
}

export interface UsePageTaskSyncOptions {
pageId: string | null
debounceMs?: number
}

export interface UsePageTaskSyncResult {
handleTasksChange: (tasks: PageTaskInput[]) => void
syncing: boolean
error: Error | null
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

interface used instead of type (AGENTS.md style guide)

AGENTS.md states: "Prefer type over interface for object shapes." The four public-API shapes added in this file all use interface:

  • export interface PageTaskReferenceInput (line 13)
  • export interface PageTaskInput (line 25)
  • export interface UsePageTaskSyncOptions (line 37)
  • export interface UsePageTaskSyncResult (line 42)

The same pattern appears throughout the PR in several other new files:

  • packages/editor/src/components/TaskMentionMenu.tsxexport interface TaskMentionSuggestion (~line 4)
  • packages/editor/src/extensions/smart-reference/providers.tsexport interface SmartReference (~line 25)
  • packages/editor/src/extensions/smart-reference/SmartReferenceExtension.tsexport interface SmartReferenceOptions (~line 34)
  • packages/editor/src/extensions/task-metadata/TaskMentionExtension.tsexport interface TaskMentionOptions (~line 40)
  • packages/editor/src/extensions/task-metadata/TaskDueDateExtension.tsexport interface TaskDueDateOptions (~line 73)

All of these should be export type declarations instead:

export type PageTaskReferenceInput = {
  url: string
  provider: string | null
  // ...
}

Context Used: Rule from dashboard - AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread apps/web/src/routes/doc.$docId.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/deploy-site.yml (1)

63-90: Consider extracting the gh-pages git/worktree publish block into a shared action.

The same publish sequence is repeated across multiple workflows; centralizing it would reduce drift and simplify future fixes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-site.yml around lines 63 - 90, Extract the repeated
"Publish production site to gh-pages" run block (the sequence that configures
git user, creates/updates the /tmp/xnet-gh-pages worktree with git worktree add
-B gh-pages, syncs files with rsync, stages changes, commits and pushes) into a
reusable GitHub Action; create an action (e.g., actions/publish-gh-pages) that
accepts inputs for source directory (default /tmp/xnet-pages-root), target
branch (gh-pages), commit message, and optional exclusions, implement the same
steps (git config, fetch, conditional git worktree add, rsync --delete
--exclude, git add/commit/push) inside the action, and replace this run block in
the workflow with a single uses: ./actions/publish-gh-pages (or the published
action) invocation passing those inputs so all workflows reuse the centralized
logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/deploy-site.yml:
- Around line 18-19: Multiple deployment workflows share the same
concurrency.group (group: pages-branch) and currently set cancel-in-progress:
true, which lets newer preview/cleanup runs cancel an in-flight production
deploy; update the concurrency block in deploy-site.yml, deploy-pr-preview.yml,
remove-pr-preview.yml, and undeploy-site.yml to set cancel-in-progress: false
(i.e., concurrency: { group: pages-branch, cancel-in-progress: false }) or
alternatively switch to unique concurrency groups (e.g., group: ${{
github.workflow }}-${{ github.ref }}) to prevent cross-workflow cancellations.

---

Nitpick comments:
In @.github/workflows/deploy-site.yml:
- Around line 63-90: Extract the repeated "Publish production site to gh-pages"
run block (the sequence that configures git user, creates/updates the
/tmp/xnet-gh-pages worktree with git worktree add -B gh-pages, syncs files with
rsync, stages changes, commits and pushes) into a reusable GitHub Action; create
an action (e.g., actions/publish-gh-pages) that accepts inputs for source
directory (default /tmp/xnet-pages-root), target branch (gh-pages), commit
message, and optional exclusions, implement the same steps (git config, fetch,
conditional git worktree add, rsync --delete --exclude, git add/commit/push)
inside the action, and replace this run block in the workflow with a single
uses: ./actions/publish-gh-pages (or the published action) invocation passing
those inputs so all workflows reuse the centralized logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2b3d3508-b233-468b-94f4-b48776087b5a

📥 Commits

Reviewing files that changed from the base of the PR and between 3e168d9 and ceef02d.

📒 Files selected for processing (4)
  • .github/workflows/deploy-pr-preview.yml
  • .github/workflows/deploy-site.yml
  • .github/workflows/remove-pr-preview.yml
  • .github/workflows/undeploy-site.yml

Comment on lines +18 to 19
group: pages-branch
cancel-in-progress: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

GitHub Actions cancel-in-progress shared concurrency group behavior multiple workflows

💡 Result:

In GitHub Actions, concurrency groups are scoped at the repository level, so different workflows share the same concurrency “slot” if they use the same concurrency.group value—and they can block/cancel each other. [1]

What happens when multiple workflows share a group

For a given concurrency group, GitHub ensures at most 1 running and 1 pending run/job at a time. If a new run is queued into the same group while another is running, the new one becomes pending, and any existing pending run in that group is canceled and replaced by the newer one. [1]

How cancel-in-progress interacts across workflows

  • If the newly-triggered run has cancel-in-progress: true, GitHub will cancel the currently running run/job in that same group—even if it’s from a different workflow. [1]
  • If the newly-triggered run has cancel-in-progress: false (or omitted), it won’t cancel the running one; it will just wait in pending (still replacing any older pending run). [1]

Avoiding cross-workflow cancellation

GitHub explicitly warns that if you have multiple workflows, concurrency group names must be unique across workflows to avoid canceling each other. A common pattern is:
group: ${{ github.workflow }}-${{ github.ref }}. [1]

Sources: GitHub Docs on workflow/job concurrency and cross-workflow group collisions/cancellation behavior. [1]


🏁 Script executed:

# Find and examine the workflow files mentioned
fd -t f "deploy-site.yml|deploy-pr-preview.yml|remove-pr-preview.yml|undeploy-site.yml" .github/workflows/

Repository: crs48/xNet

Length of output: 203


🏁 Script executed:

# Check deploy-site.yml concurrency section
head -n 25 .github/workflows/deploy-site.yml | tail -n 10

Repository: crs48/xNet

Length of output: 205


🏁 Script executed:

# Check all four workflows for their concurrency groups
for file in .github/workflows/deploy-site.yml .github/workflows/deploy-pr-preview.yml .github/workflows/remove-pr-preview.yml .github/workflows/undeploy-site.yml; do
  echo "=== $file ==="
  grep -A 5 "^concurrency:" "$file" || echo "No concurrency found"
done

Repository: crs48/xNet

Length of output: 569


Remove cancel-in-progress: true from shared concurrency group to prevent production deploys from being canceled.

When multiple workflows share the same concurrency.group, setting cancel-in-progress: true allows any newly-triggered workflow to cancel currently running jobs from other workflows in that group. With all four deployment workflows (deploy-site.yml, deploy-pr-preview.yml, remove-pr-preview.yml, undeploy-site.yml) using group: pages-branch, a preview or cleanup run can cancel an in-flight production deploy, leaving gh-pages stale.

Set cancel-in-progress: false in all four workflows:

Changes needed
  • .github/workflows/deploy-site.yml (line 19)
  • .github/workflows/deploy-pr-preview.yml
  • .github/workflows/remove-pr-preview.yml
  • .github/workflows/undeploy-site.yml

Change from cancel-in-progress: true to cancel-in-progress: false in each.

Alternatively, use unique concurrency groups per workflow (e.g., group: ${{ github.workflow }}-${{ github.ref }}) to prevent cross-workflow interference.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-site.yml around lines 18 - 19, Multiple deployment
workflows share the same concurrency.group (group: pages-branch) and currently
set cancel-in-progress: true, which lets newer preview/cleanup runs cancel an
in-flight production deploy; update the concurrency block in deploy-site.yml,
deploy-pr-preview.yml, remove-pr-preview.yml, and undeploy-site.yml to set
cancel-in-progress: false (i.e., concurrency: { group: pages-branch,
cancel-in-progress: false }) or alternatively switch to unique concurrency
groups (e.g., group: ${{ github.workflow }}-${{ github.ref }}) to prevent
cross-workflow cancellations.

crs added 3 commits March 5, 2026 18:41
- add a useTasks hook for page and assignee task queries

- fix page task sync UTC due dates and reference write order

- add My Tasks and page task views in web and page task view in Electron

- extend hook coverage for subscribed task view updates

- check off the completed exploration items
- add a taskViewEmbed editor block with persisted scope, assignee, status,
  due-date, and hierarchy filters
- wire embedded task rendering through the shared editor surface in web and
  desktop page views
- extend task queries with due-date presets and update the exploration
  checklist for the shipped slice
- add gravatar-backed avatars to task mention suggestions and the autocomplete menu
- include the current user in page mention suggestions on web and desktop
- cover the gravatar helper and mention menu avatar rendering with focused tests

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (2)
packages/editor/src/components/RichTextEditor.tsx (1)

515-516: ⚠️ Potential issue | 🟠 Major

Register the update listener before the initial publish.

Line 515 runs publishPageTasks() before Line 516 registers the listener, so an update emitted during ensurePageTaskAttrs(editor) can be dropped.

Suggested fix
-    publishPageTasks()
-    editor.on('update', publishPageTasks)
+    editor.on('update', publishPageTasks)
+    publishPageTasks()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/components/RichTextEditor.tsx` around lines 515 - 516,
The initial call to publishPageTasks() runs before registering the editor.update
listener, so updates emitted during ensurePageTaskAttrs(editor) can be missed;
fix by registering the listener first (call editor.on('update',
publishPageTasks) before calling publishPageTasks()) so that any update fired
during ensurePageTaskAttrs(editor) will be handled by the publishPageTasks
handler.
apps/web/src/routes/doc.$docId.tsx (1)

102-102: ⚠️ Potential issue | 🟡 Minor

Use a block-bodied forEach callback to satisfy Biome.

Line 102 still triggers lint/suspicious/useIterableCallbackReturn.

🔧 Suggested fix
-    presence.forEach((user) => addSuggestion(user))
+    presence.forEach((user) => {
+      addSuggestion(user)
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/doc`.$docId.tsx at line 102, The one-line arrow callback
used with presence.forEach is flagged by the linter; change the callback to a
block-bodied form so the forEach invocation becomes a callback that calls
addSuggestion(user) inside a block (e.g., presence.forEach((user) => {
addSuggestion(user); })), ensuring the symbol presence.forEach and the function
addSuggestion are used unchanged but the callback has a block body to satisfy
Biome's lint rule.
🧹 Nitpick comments (12)
packages/editor/src/components/RichTextEditor.tsx (1)

4-14: Reorder type imports to match repository import-order rules.

Lines 4-14 currently place a local relative type import before external type imports. Move external type-only imports first, and keep local relative imports last.

As per coding guidelines: “Type-only imports must come first”, “Internal package imports should follow external packages”, and “Local relative imports should come last”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/components/RichTextEditor.tsx` around lines 4 - 14,
Reorder the type imports in RichTextEditor.tsx so external type-only imports
come first and the local relative import comes last: move the imports from
'@tiptap/core' (AnyExtension), 'y-protocols/awareness' (Awareness), and 'yjs'
(Y) to appear before the local '../extensions' type import (DatabaseViewType,
PageTaskSnapshot, SlashCommandItem, TaskMentionSuggestion, TaskViewConfig,
TaskViewEmbedType) so the module order follows the repository rules (external
packages, then internal packages, then local relative).
apps/web/src/components/PageTasksPanel.tsx (1)

5-7: Use a type alias for props to match project convention.

Convert PageTasksPanelProps from interface to type for consistency with the repo rule.

Suggested fix
-interface PageTasksPanelProps {
+type PageTasksPanelProps = {
   pageId: string
-}
+}

As per coding guidelines: “Prefer type over interface for defining object shapes in TypeScript”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/PageTasksPanel.tsx` around lines 5 - 7, Replace the
PageTasksPanelProps interface with a type alias to follow the project
convention: change the declaration named PageTasksPanelProps from an `interface`
to a `type` (e.g., `type PageTasksPanelProps = { pageId: string }`) and ensure
any usages inside the PageTasksPanel component still refer to
PageTasksPanelProps without other changes.
packages/editor/src/extensions/task-view-embed/index.ts (1)

1-1: Add the required package file-level JSDoc header.

This new packages/* file starts directly with exports; add the required top-of-file JSDoc banner.

As per coding guidelines "packages/**/*.{ts,tsx}: Include file-level JSDoc at top of file".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/extensions/task-view-embed/index.ts` at line 1, This file
is missing the required top-of-file JSDoc banner; add a file-level JSDoc comment
at the very top of the file (above the existing export line) describing the
module and any relevant metadata per project guidelines so that the file
exporting TaskViewEmbedExtension and DEFAULT_TASK_VIEW_CONFIG includes the
mandated header; ensure the comment follows the same JSDoc format used across
packages (brief description, optional `@module/`@file or author/license tags if
used in other files).
apps/web/src/components/Editor.tsx (1)

42-53: Add an explicit return type for the exported Editor component.

Please annotate Editor with an explicit JSX return type for consistency with repository TS rules.

As per coding guidelines "Use explicit return types on exported functions".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/Editor.tsx` around lines 42 - 53, The exported Editor
function lacks an explicit return type; update its signature (the export
function Editor(...) declaration) to include a JSX return type (e.g., ):
JSX.Element or React.JSX.Element) so the exported component has an explicit
return type per project rules; adjust any imports if your chosen annotation
requires them and ensure the Props type remains unchanged.
packages/editor/src/extensions/task-view-embed/TaskViewEmbedNodeView.tsx (1)

1-190: Add required package-level documentation scaffolding.

This new packages/editor file is missing the required file-level JSDoc and section/doc conventions for exported functionality.

As per coding guidelines "packages/**/*.{ts,tsx}: Include file-level JSDoc at top of file", "Use section dividers in code", and "Function documentation should include JSDoc with @example tags".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/extensions/task-view-embed/TaskViewEmbedNodeView.tsx`
around lines 1 - 190, Add the required file-level JSDoc and section dividers and
add JSDoc for exported items: insert a top-of-file JSDoc block describing the
package/editor extension file, its purpose and exports; add clear section
dividers (e.g., “// Constants”, “// Helpers”, “// Components / NodeViews”)
around SCOPE_OPTIONS/ASSIGNEE_OPTIONS/STATUS_OPTIONS/DUE_DATE_OPTIONS, getConfig
and FilterSelect, and the main exported function; and add a JSDoc comment for
the exported TaskViewEmbedNodeView function that documents parameters and
includes an `@example` showing basic usage (call signature showing node props or
usage with extension.options.renderView). Ensure the exported symbol name
TaskViewEmbedNodeView and helper names getConfig and FilterSelect are referenced
in the comments where appropriate.
apps/web/src/components/MyTasksPanel.tsx (1)

1-5: Align import grouping and exported signature with TS guidelines.

Please reorder imports so all external packages come before internal @xnetjs/* imports, and add an explicit return type to MyTasksPanel.

As per coding guidelines "Internal package imports should follow external packages" and "Use explicit return types on exported functions".

Also applies to: 20-20

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/MyTasksPanel.tsx` around lines 1 - 5, Reorder the
import block so all external packages (e.g., '@tanstack/react-router',
'lucide-react', 'react' / 'useMemo, useState') appear before internal `@xnetjs/`*
imports (move "PageSchema", "useIdentity", "useQuery", "useTasks" after external
imports), and add an explicit return type to the exported component signature
for MyTasksPanel (e.g., export function MyTasksPanel(...): JSX.Element { ... })
so the function has a clear typed return; apply the same import ordering and
explicit return type adjustment to the other occurrence noted.
apps/electron/src/renderer/components/PageTasksPanel.tsx (2)

5-7: Align component typing with repository TS conventions.

Use a type alias for props and add an explicit return type on the exported component.

As per coding guidelines "Prefer type over interface for defining object shapes in TypeScript" and "Use explicit return types on exported functions".

Also applies to: 22-22

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/PageTasksPanel.tsx` around lines 5 - 7,
Replace the interface PageTasksPanelProps with a type alias (e.g., type
PageTasksPanelProps = { pageId: string }) and update the exported component
declaration to include an explicit return type (e.g., export const
PageTasksPanel = (props: PageTasksPanelProps): JSX.Element => { ... }); ensure
you update any other occurrence noted (the similar definition at the other spot)
to follow the same pattern so props use a type alias and the component has an
explicit JSX.Element return type.

22-115: Extract this panel into shared code to prevent web/electron drift.

This implementation mirrors apps/web/src/components/PageTasksPanel.tsx line-for-line. Consider one shared component to avoid dual maintenance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/PageTasksPanel.tsx` around lines 22 -
115, The PageTasksPanel component is duplicated in web and electron; extract a
single shared component to avoid drift by moving the implementation of
PageTasksPanel (which uses useTasks, formatDueDate, isOverdue and icons like
CheckSquare2, Square, Calendar, Users) into a shared UI package and have both
apps import it; update the electron and web exports to import the shared
PageTasksPanel and ensure any app-specific dependencies (styling classes or icon
imports) are either re-exported from the shared package or injected via props
(e.g., pass icons or className overrides) so the shared component compiles in
both environments.
packages/react/src/components/TaskCollectionEmbed.tsx (1)

1-12: Apply package TS conventions for new public props typing/file header.

Please switch TaskCollectionEmbedProps to a type alias and add the required package file-level JSDoc header.

As per coding guidelines "Prefer type over interface for defining object shapes in TypeScript" and "packages/**/*.{ts,tsx}: Include file-level JSDoc at top of file".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/components/TaskCollectionEmbed.tsx` around lines 1 - 12,
Replace the exported interface TaskCollectionEmbedProps with a type alias (e.g.,
"type TaskCollectionEmbedProps = { ... }") preserving the same property names
and types, and add the required file-level JSDoc header at the very top of the
TaskCollectionEmbed.tsx file (above imports) following package conventions;
update any references to TaskCollectionEmbedProps if needed to ensure
exports/imports remain correct.
apps/electron/src/renderer/components/PageView.tsx (1)

107-131: Extract mention-suggestion building into a shared helper.

This logic is duplicated with apps/web/src/routes/doc.$docId.tsx and will drift. A shared utility (web/electron) would keep behavior consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/PageView.tsx` around lines 107 - 131,
Extract the duplicate mention-suggestion logic into a shared helper (e.g.,
buildMentionSuggestions) and replace the inline useMemo in PageView with a call
to that helper: move the Map creation, addSuggestion function, and final
Array.from(suggestions.values()) into the new utility which accepts (presence,
did, optional isLocal label/subtitle override) and returns the suggestions
array; update PageView to import and call buildMentionSuggestions(presence, did)
(and likewise update apps/web/src/routes/doc.$docId.tsx to use the same helper)
so both locations reuse the same mentionSuggestions construction logic
(referencing the existing symbols mentionSuggestions, addSuggestion, presence,
did, and the useMemo wrapper).
packages/react/src/hooks/useTasks.ts (1)

12-33: Use type aliases for these object shapes to match repo standards.

These exported shapes should use type instead of interface for consistency with project conventions.

Suggested refactor
-export interface UseTasksOptions {
+export type UseTasksOptions = {
   pageId?: string | null
   assigneeDid?: string | null
   includeCompleted?: boolean
   statuses?: TaskStatus[]
   parentTaskId?: string | null
   dueDateFilter?: 'any' | 'overdue' | 'today' | 'next-7-days' | 'none'
-}
+}

-export interface TaskTreeItem {
+export type TaskTreeItem = {
   task: TaskNode
   depth: number
   children: TaskTreeItem[]
-}
+}

-export interface UseTasksResult {
+export type UseTasksResult = {
   data: TaskNode[]
   tree: TaskTreeItem[]
   loading: boolean
   error: Error | null
   reload: () => void
-}
+}
As per coding guidelines, "Prefer `type` over `interface` for defining object shapes in TypeScript".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/hooks/useTasks.ts` around lines 12 - 33, Replace the
exported interfaces UseTasksOptions, TaskTreeItem, and UseTasksResult with
equivalent exported type aliases to match repo standards; keep the exact
exported names and all properties (pageId, assigneeDid, includeCompleted,
statuses, parentTaskId, dueDateFilter on UseTasksOptions; task, depth, children
on TaskTreeItem; data, tree, loading, error, reload on UseTasksResult) and their
types unchanged, ensuring no behavioral or API changes where these symbols are
used elsewhere.
packages/editor/src/extensions/task-view-embed/TaskViewEmbedExtension.ts (1)

11-25: Prefer type aliases over interface for these exported object shapes.

This keeps the file aligned with the repository TypeScript conventions.

As per coding guidelines, "Prefer type over interface for defining object shapes in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/extensions/task-view-embed/TaskViewEmbedExtension.ts`
around lines 11 - 25, Replace the exported interfaces with equivalent type
aliases: change the exported TaskViewConfig interface to a type alias named
TaskViewConfig and change TaskViewEmbedOptions interface to a type alias named
TaskViewEmbedOptions (preserving the same property names and types such as
scope, assignee, dueDate, status, showHierarchy and the
renderView/HTMLAttributes shape), ensuring the renderView callback signature and
React.ReactNode return type remain unchanged; update any local imports/uses if
necessary to match the type alias exports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/electron/src/renderer/components/PageView.tsx`:
- Line 128: The forEach callback using presence.forEach((user) =>
addSuggestion(user)) triggers lint/suspicious/useIterableCallbackReturn; change
it to a block-bodied callback so the return is explicit and side-effects are
clear—replace the concise arrow with a block-bodied arrow function that calls
addSuggestion(user) inside (e.g., presence.forEach((user) => {
addSuggestion(user); })); update the callsite in PageView where presence.forEach
is invoked to use this form.

In `@apps/web/src/components/MyTasksPanel.tsx`:
- Around line 25-29: The hardcoded limit in the Page fetch (useQuery(PageSchema,
{ limit: 200 })) causes pageTitles (computed in the useMemo that maps pages to
titles) to miss pages beyond 200; change the fetch to either remove the limit
or, better, fetch only the pages referenced by tasks by passing a filter with
the set of task.page IDs (e.g. build an array of unique task.page IDs and call
useQuery(PageSchema, { filter: { id: { in: [...] } } })), then recompute
pageTitles in the useMemo based on that result so all linked tasks get their
title chips.

In `@apps/web/src/components/PageTasksPanel.tsx`:
- Around line 18-20: isOverdue() currently compares raw milliseconds against
Date.now() which conflicts with other logic that normalizes due dates to UTC
midnight; update isOverdue (in PageTasksPanel.tsx) to perform a day-boundary
check like the rest of the codebase by comparing the task's UTC-day timestamp
against the start of today in UTC (use the same helper used elsewhere, e.g.
getStartOfUtcDay() or the toDateTimestamp() normalization) so overdue only
becomes true after the entire due day has passed.

In
`@docs/explorations/0103_`[_]_TASKS_EMBEDDED_IN_PAGES_BACKED_BY_NODES_MENTIONS_DUE_DATES_NESTED_SUBTASKS_DATABASES_CANVASES_AND_CROSS_SURFACE_TASK_MODEL.md:
- Line 220: The inline code span contains a trailing space (`[] `) which
triggers MD038; edit the markdown at the occurrence of the code span (the `[] `
token) and remove the trailing space so it becomes `` `[]` ``, ensuring the code
span has no internal trailing whitespace.

In `@packages/editor/src/extensions/task-view-embed/TaskViewEmbedExtension.ts`:
- Around line 35-37: parseTaskViewConfig currently returns the shared
DEFAULT_TASK_VIEW_CONFIG constant on fallback, which risks cross-node mutations;
change it to return a fresh copy instead (e.g., clone DEFAULT_TASK_VIEW_CONFIG
via structured cloning or by shallow copying the top-level object and
deep-copying nested arrays/objects) so callers get an independent object; update
every other fallback in this file that returns DEFAULT_TASK_VIEW_CONFIG to do
the same (search for other returns of DEFAULT_TASK_VIEW_CONFIG and replace with
a fresh-clone expression).
- Around line 141-152: The updateTaskViewEmbed action currently replaces the
entire viewConfig with DEFAULT_TASK_VIEW_CONFIG merged only with
options.viewConfig, which resets unspecified fields; fix it by first reading the
existing attributes for this.name (e.g., via
commands.editor?.getAttributes(this.name) or the current node attrs), then
construct viewConfig as { ...DEFAULT_TASK_VIEW_CONFIG, ...(existing.viewConfig
?? {}), ...(options.viewConfig ?? {}) } and pass that to
commands.updateAttributes(this.name, { ...(options.viewType !== undefined && {
viewType: options.viewType }), viewConfig }); this preserves previously set
fields while applying partial updates.

In `@packages/react/src/hooks/useTasks.ts`:
- Around line 114-143: buildTaskTree currently sets depth while linking nodes
which makes depth order-dependent; instead first build all TaskTreeItem nodes in
treeById and link parent/child relationships (as you already do), then compute
depths by traversing from root items: find roots (nodes with no parent) and run
a simple DFS/BFS that calls a helper like setDepth(node, depth) to assign
node.depth = depth and then recurse into node.children with depth+1; update
references to TaskNode/TaskTreeItem and the treeById/roots variables so depth
assignment happens only during this traversal.

---

Duplicate comments:
In `@apps/web/src/routes/doc`.$docId.tsx:
- Line 102: The one-line arrow callback used with presence.forEach is flagged by
the linter; change the callback to a block-bodied form so the forEach invocation
becomes a callback that calls addSuggestion(user) inside a block (e.g.,
presence.forEach((user) => { addSuggestion(user); })), ensuring the symbol
presence.forEach and the function addSuggestion are used unchanged but the
callback has a block body to satisfy Biome's lint rule.

In `@packages/editor/src/components/RichTextEditor.tsx`:
- Around line 515-516: The initial call to publishPageTasks() runs before
registering the editor.update listener, so updates emitted during
ensurePageTaskAttrs(editor) can be missed; fix by registering the listener first
(call editor.on('update', publishPageTasks) before calling publishPageTasks())
so that any update fired during ensurePageTaskAttrs(editor) will be handled by
the publishPageTasks handler.

---

Nitpick comments:
In `@apps/electron/src/renderer/components/PageTasksPanel.tsx`:
- Around line 5-7: Replace the interface PageTasksPanelProps with a type alias
(e.g., type PageTasksPanelProps = { pageId: string }) and update the exported
component declaration to include an explicit return type (e.g., export const
PageTasksPanel = (props: PageTasksPanelProps): JSX.Element => { ... }); ensure
you update any other occurrence noted (the similar definition at the other spot)
to follow the same pattern so props use a type alias and the component has an
explicit JSX.Element return type.
- Around line 22-115: The PageTasksPanel component is duplicated in web and
electron; extract a single shared component to avoid drift by moving the
implementation of PageTasksPanel (which uses useTasks, formatDueDate, isOverdue
and icons like CheckSquare2, Square, Calendar, Users) into a shared UI package
and have both apps import it; update the electron and web exports to import the
shared PageTasksPanel and ensure any app-specific dependencies (styling classes
or icon imports) are either re-exported from the shared package or injected via
props (e.g., pass icons or className overrides) so the shared component compiles
in both environments.

In `@apps/electron/src/renderer/components/PageView.tsx`:
- Around line 107-131: Extract the duplicate mention-suggestion logic into a
shared helper (e.g., buildMentionSuggestions) and replace the inline useMemo in
PageView with a call to that helper: move the Map creation, addSuggestion
function, and final Array.from(suggestions.values()) into the new utility which
accepts (presence, did, optional isLocal label/subtitle override) and returns
the suggestions array; update PageView to import and call
buildMentionSuggestions(presence, did) (and likewise update
apps/web/src/routes/doc.$docId.tsx to use the same helper) so both locations
reuse the same mentionSuggestions construction logic (referencing the existing
symbols mentionSuggestions, addSuggestion, presence, did, and the useMemo
wrapper).

In `@apps/web/src/components/Editor.tsx`:
- Around line 42-53: The exported Editor function lacks an explicit return type;
update its signature (the export function Editor(...) declaration) to include a
JSX return type (e.g., ): JSX.Element or React.JSX.Element) so the exported
component has an explicit return type per project rules; adjust any imports if
your chosen annotation requires them and ensure the Props type remains
unchanged.

In `@apps/web/src/components/MyTasksPanel.tsx`:
- Around line 1-5: Reorder the import block so all external packages (e.g.,
'@tanstack/react-router', 'lucide-react', 'react' / 'useMemo, useState') appear
before internal `@xnetjs/`* imports (move "PageSchema", "useIdentity", "useQuery",
"useTasks" after external imports), and add an explicit return type to the
exported component signature for MyTasksPanel (e.g., export function
MyTasksPanel(...): JSX.Element { ... }) so the function has a clear typed
return; apply the same import ordering and explicit return type adjustment to
the other occurrence noted.

In `@apps/web/src/components/PageTasksPanel.tsx`:
- Around line 5-7: Replace the PageTasksPanelProps interface with a type alias
to follow the project convention: change the declaration named
PageTasksPanelProps from an `interface` to a `type` (e.g., `type
PageTasksPanelProps = { pageId: string }`) and ensure any usages inside the
PageTasksPanel component still refer to PageTasksPanelProps without other
changes.

In `@packages/editor/src/components/RichTextEditor.tsx`:
- Around line 4-14: Reorder the type imports in RichTextEditor.tsx so external
type-only imports come first and the local relative import comes last: move the
imports from '@tiptap/core' (AnyExtension), 'y-protocols/awareness' (Awareness),
and 'yjs' (Y) to appear before the local '../extensions' type import
(DatabaseViewType, PageTaskSnapshot, SlashCommandItem, TaskMentionSuggestion,
TaskViewConfig, TaskViewEmbedType) so the module order follows the repository
rules (external packages, then internal packages, then local relative).

In `@packages/editor/src/extensions/task-view-embed/index.ts`:
- Line 1: This file is missing the required top-of-file JSDoc banner; add a
file-level JSDoc comment at the very top of the file (above the existing export
line) describing the module and any relevant metadata per project guidelines so
that the file exporting TaskViewEmbedExtension and DEFAULT_TASK_VIEW_CONFIG
includes the mandated header; ensure the comment follows the same JSDoc format
used across packages (brief description, optional `@module/`@file or
author/license tags if used in other files).

In `@packages/editor/src/extensions/task-view-embed/TaskViewEmbedExtension.ts`:
- Around line 11-25: Replace the exported interfaces with equivalent type
aliases: change the exported TaskViewConfig interface to a type alias named
TaskViewConfig and change TaskViewEmbedOptions interface to a type alias named
TaskViewEmbedOptions (preserving the same property names and types such as
scope, assignee, dueDate, status, showHierarchy and the
renderView/HTMLAttributes shape), ensuring the renderView callback signature and
React.ReactNode return type remain unchanged; update any local imports/uses if
necessary to match the type alias exports.

In `@packages/editor/src/extensions/task-view-embed/TaskViewEmbedNodeView.tsx`:
- Around line 1-190: Add the required file-level JSDoc and section dividers and
add JSDoc for exported items: insert a top-of-file JSDoc block describing the
package/editor extension file, its purpose and exports; add clear section
dividers (e.g., “// Constants”, “// Helpers”, “// Components / NodeViews”)
around SCOPE_OPTIONS/ASSIGNEE_OPTIONS/STATUS_OPTIONS/DUE_DATE_OPTIONS, getConfig
and FilterSelect, and the main exported function; and add a JSDoc comment for
the exported TaskViewEmbedNodeView function that documents parameters and
includes an `@example` showing basic usage (call signature showing node props or
usage with extension.options.renderView). Ensure the exported symbol name
TaskViewEmbedNodeView and helper names getConfig and FilterSelect are referenced
in the comments where appropriate.

In `@packages/react/src/components/TaskCollectionEmbed.tsx`:
- Around line 1-12: Replace the exported interface TaskCollectionEmbedProps with
a type alias (e.g., "type TaskCollectionEmbedProps = { ... }") preserving the
same property names and types, and add the required file-level JSDoc header at
the very top of the TaskCollectionEmbed.tsx file (above imports) following
package conventions; update any references to TaskCollectionEmbedProps if needed
to ensure exports/imports remain correct.

In `@packages/react/src/hooks/useTasks.ts`:
- Around line 12-33: Replace the exported interfaces UseTasksOptions,
TaskTreeItem, and UseTasksResult with equivalent exported type aliases to match
repo standards; keep the exact exported names and all properties (pageId,
assigneeDid, includeCompleted, statuses, parentTaskId, dueDateFilter on
UseTasksOptions; task, depth, children on TaskTreeItem; data, tree, loading,
error, reload on UseTasksResult) and their types unchanged, ensuring no
behavioral or API changes where these symbols are used elsewhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9e85203e-97d7-43cb-a1ce-0f2118dbfa9b

📥 Commits

Reviewing files that changed from the base of the PR and between ceef02d and 9247fe8.

📒 Files selected for processing (22)
  • apps/electron/src/renderer/components/PageTasksPanel.tsx
  • apps/electron/src/renderer/components/PageView.tsx
  • apps/web/src/components/Editor.tsx
  • apps/web/src/components/MyTasksPanel.tsx
  • apps/web/src/components/PageTasksPanel.tsx
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/routes/doc.$docId.tsx
  • docs/explorations/0103_[_]_TASKS_EMBEDDED_IN_PAGES_BACKED_BY_NODES_MENTIONS_DUE_DATES_NESTED_SUBTASKS_DATABASES_CANVASES_AND_CROSS_SURFACE_TASK_MODEL.md
  • packages/editor/src/components/RichTextEditor.tsx
  • packages/editor/src/extensions.ts
  • packages/editor/src/extensions/slash-command/items.ts
  • packages/editor/src/extensions/task-view-embed/TaskViewEmbedExtension.test.ts
  • packages/editor/src/extensions/task-view-embed/TaskViewEmbedExtension.ts
  • packages/editor/src/extensions/task-view-embed/TaskViewEmbedNodeView.tsx
  • packages/editor/src/extensions/task-view-embed/index.ts
  • packages/editor/src/react.ts
  • packages/react/src/components/TaskCollectionEmbed.tsx
  • packages/react/src/hooks/usePageTaskSync.test.tsx
  • packages/react/src/hooks/usePageTaskSync.ts
  • packages/react/src/hooks/useTasks.test.tsx
  • packages/react/src/hooks/useTasks.ts
  • packages/react/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/react/src/hooks/usePageTaskSync.test.tsx
  • packages/react/src/hooks/usePageTaskSync.ts
  • packages/react/src/index.ts

Comment thread apps/electron/src/renderer/components/PageView.tsx Outdated
Comment thread apps/web/src/components/MyTasksPanel.tsx Outdated
Comment on lines +18 to +20
function isOverdue(timestamp: number | undefined, completed: boolean): boolean {
return typeof timestamp === 'number' && !completed && timestamp < Date.now()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and inspect the files mentioned in the review

# 1) Check usePageTaskSync.ts for dueDate timestamp conversion
echo "=== Checking usePageTaskSync.ts ===" 
find . -type f -name "usePageTaskSync.ts" | head -5

# 2) Check useTasks.ts for filter logic
echo "=== Checking useTasks.ts ===" 
find . -type f -name "useTasks.ts" | head -5

# 3) Also look for PageTasksPanel.tsx to verify the context
echo "=== Checking PageTasksPanel.tsx ===" 
find . -type f -name "PageTasksPanel.tsx" | head -5

Repository: crs48/xNet

Length of output: 343


🏁 Script executed:

#!/bin/bash
# Search for dueDate and timestamp related code
rg -n 'dueDate|toDateTimestamp|matchesDueDate' --type ts --type tsx | head -50

Repository: crs48/xNet

Length of output: 81


🏁 Script executed:

#!/bin/bash
# Search for dueDate and timestamp handling without file type restriction
echo "=== Searching for dueDate and timestamp patterns ===" 
rg -n 'dueDate|toDateTimestamp|matchesDueDate' -A 2 -B 2 | head -80

# Also check the PageTasksPanel.tsx file directly
echo "=== Checking PageTasksPanel.tsx ===" 
cat -n ./apps/web/src/components/PageTasksPanel.tsx | head -40

Repository: crs48/xNet

Length of output: 6822


🏁 Script executed:

#!/bin/bash
# Check usePageTaskSync.ts for how dueDate is processed
echo "=== usePageTaskSync.ts content ===" 
wc -l ./packages/react/src/hooks/usePageTaskSync.ts
head -100 ./packages/react/src/hooks/usePageTaskSync.ts

Repository: crs48/xNet

Length of output: 3058


🏁 Script executed:

#!/bin/bash
# Check useTasks.ts for filter/comparison logic
echo "=== useTasks.ts content ===" 
wc -l ./packages/react/src/hooks/useTasks.ts
cat -n ./packages/react/src/hooks/useTasks.ts | head -100

Repository: crs48/xNet

Length of output: 4340


🏁 Script executed:

#!/bin/bash
# Get the complete matchesDueDate function from useTasks.ts
sed -n '92,130p' ./packages/react/src/hooks/useTasks.ts

Repository: crs48/xNet

Length of output: 1163


🏁 Script executed:

#!/bin/bash
# Check if there's a date conversion happening - search for where dueDate string becomes number
rg -n 'dueDate.*timestamp|toDate|parseDate|new Date.*dueDate' ./packages/react/src/hooks/ -A 2

Repository: crs48/xNet

Length of output: 663


🏁 Script executed:

#!/bin/bash
# Get the complete toDateTimestamp function
sed -n '106,120p' ./packages/react/src/hooks/usePageTaskSync.ts

Repository: crs48/xNet

Length of output: 552


🏁 Script executed:

#!/bin/bash
# Also check if there's any documentation or usage that clarifies the timezone handling
rg -n 'getStartOfUtcDay|toDateTimestamp|dueDate.*UTC' ./packages/react/src/hooks/ -B 2 -A 3

Repository: crs48/xNet

Length of output: 3143


isOverdue() uses raw millisecond comparison but timestamps are normalized to UTC day boundaries.

Line 19 compares timestamp < Date.now() while toDateTimestamp() (usePageTaskSync.ts) stores due dates as UTC midnight via Date.UTC(year, month - 1, day). Meanwhile, useTasks.ts implements matchesDueDate('overdue') using day-boundary comparison with getStartOfUtcDay(). This causes inconsistent overdue semantics: PageTasksPanel marks tasks overdue immediately after midnight UTC, while useTasks marks them overdue only after the entire due day passes. Align isOverdue() to use day-level semantics matching the rest of the task filtering logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/PageTasksPanel.tsx` around lines 18 - 20, isOverdue()
currently compares raw milliseconds against Date.now() which conflicts with
other logic that normalizes due dates to UTC midnight; update isOverdue (in
PageTasksPanel.tsx) to perform a day-boundary check like the rest of the
codebase by comparing the task's UTC-day timestamp against the start of today in
UTC (use the same helper used elsewhere, e.g. getStartOfUtcDay() or the
toDateTimestamp() normalization) so overdue only becomes true after the entire
due day has passed.

Comment on lines +53 to +63
const pageId = scope === 'current-page' ? currentPageId : undefined
const assigneeDid = assignee === 'me' ? currentDid : undefined
const statuses = status === 'done' ? (['done'] as const) : undefined
const includeCompleted = status !== 'open'
const { data, tree, loading } = useTasks({
pageId,
assigneeDid,
includeCompleted,
statuses: statuses ? [...statuses] : undefined,
dueDateFilter: dueDate
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing page context still triggers a global task query.

When scope is 'current-page' and currentPageId is null, Line 53 passes a falsy pageId, so useTasks falls back to an unscoped query before Line 77 returns the context warning.

🔧 Suggested fix
 export function TaskCollectionEmbed({
@@
 }: TaskCollectionEmbedProps): JSX.Element {
-  const pageId = scope === 'current-page' ? currentPageId : undefined
+  const missingPageContext = scope === 'current-page' && !currentPageId
+  const pageId =
+    scope === 'current-page'
+      ? (currentPageId ?? '__task_collection_embed_missing_page_context__')
+      : undefined
@@
   const { data, tree, loading } = useTasks({
@@
   })
 
-  if (scope === 'current-page' && !currentPageId) {
+  if (missingPageContext) {
     return <div className="p-4 text-sm text-muted-foreground">This view needs a page context.</div>
   }

Also applies to: 77-79

Comment thread packages/react/src/hooks/useTasks.ts
- replace single-run edge bundling timing with a median of repeated samples
- scale canvas and view performance budgets for local versus CI variance
- keep the assertions as regression guards without depending on micro-benchmark precision

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
apps/electron/src/renderer/components/PageView.tsx (1)

130-130: ⚠️ Potential issue | 🟡 Minor

Biome lint issue is still present on the presence.forEach callback.

Switch to a block-bodied callback so forEach does not implicitly return a value.

🔧 Suggested fix
-    presence.forEach((user) => addSuggestion(user))
+    presence.forEach((user) => {
+      addSuggestion(user)
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/PageView.tsx` at line 130, The forEach
callback currently uses a concise arrow body (presence.forEach((user) =>
addSuggestion(user))) which causes an implicit return and triggers the Biome
lint rule; change it to a block-bodied callback so it does not return a value
(e.g., presence.forEach((user) => { addSuggestion(user); })) — update the
occurrence in the PageView component where presence.forEach is called and ensure
addSuggestion is invoked inside a block-bodied arrow function.
apps/web/src/routes/doc.$docId.tsx (1)

103-103: ⚠️ Potential issue | 🟡 Minor

Biome lint issue is still present on the presence.forEach callback.

Use a block-bodied callback to avoid returning a value from forEach.

🔧 Suggested fix
-    presence.forEach((user) => addSuggestion(user))
+    presence.forEach((user) => {
+      addSuggestion(user)
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/doc`.$docId.tsx at line 103, The forEach callback for
presence.forEach currently uses a concise arrow body which can implicitly return
a value; change it to a block-bodied callback so nothing is returned—replace
presence.forEach((user) => addSuggestion(user)) with a block-bodied form that
calls addSuggestion(user) inside braces (e.g., presence.forEach((user) => {
addSuggestion(user); })), ensuring the callback has no return value and
referencing the presence.forEach and addSuggestion symbols.
🧹 Nitpick comments (3)
packages/editor/src/utils/gravatar.ts (1)

1-94: Document this new package utility module per repo standards.

Please add a file-level JSDoc header and JSDoc blocks (with @example) for exported helpers (createGravatarUrl, md5) so this module aligns with package conventions.

As per coding guidelines: packages/**/*.{ts,tsx}: Include file-level JSDoc at top of file and Function documentation should include JSDoc with @example tags.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/utils/gravatar.ts` around lines 1 - 94, Add a file-level
JSDoc header at the top of the file and JSDoc blocks for the exported functions
createGravatarUrl and md5 (including `@param`, `@returns` and an `@example` for each)
so the module follows package documentation conventions; update the JSDoc to
describe the module purpose (Gravatar utility), document
createGravatarUrl(input: string, size?: number) and md5(input: string) with
concise descriptions and usage examples, and ensure exported symbols
(createGravatarUrl, md5) are referenced in their respective comments.
packages/editor/src/components/TaskMentionMenu.test.tsx (1)

1-1: Add the package-level JSDoc header to this new test module.

This file currently starts with imports; package files are expected to begin with the standard file-level JSDoc banner.

🔧 Suggested change
+/**
+ * `@xnetjs/editor` - Task mention menu tests
+ */
 import { render, screen } from '@testing-library/react'

As per coding guidelines: packages/**/*.{ts,tsx}: Include file-level JSDoc at top of file: /** @xnetjs/sync - Unified sync primitives for xNet */.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/editor/src/components/TaskMentionMenu.test.tsx` at line 1, This test
module (TaskMentionMenu.test.tsx) is missing the required file-level JSDoc
banner; add the standard package-level JSDoc comment as the very first lines of
the file (before any imports) using the exact header string: /** `@xnetjs/sync` -
Unified sync primitives for xNet */ so the file begins with that banner then the
existing imports (e.g., render, screen from '@testing-library/react').
apps/electron/src/renderer/components/PageView.tsx (1)

780-810: Add an Electron Playwright path for this new integrated editor flow.

This segment now wires mentions, page-task sync, task-view rendering, and panel UI together; a focused renderer e2e would reduce regression risk.

Based on learnings: Test UI changes in Electron with Playwright after implementing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/PageView.tsx` around lines 780 - 810,
Add an Electron Playwright E2E path that exercises the new integrated editor
flow wired here: render the editor with mentionSuggestions, trigger
onPageTasksChange (handleTasksChange) and verify renderTaskView via
TaskCollectionEmbed props (scope/assignee/dueDate/status/showHierarchy), ensure
OrphanedThreadList appears and can toggle/dismiss/reattach
(onToggleCollapse/onDismiss/onReattach/onSelect) and that PageTasksPanel (pageId
prop) is visible and syncs with task changes; create a new Playwright test under
the Electron e2e suite that launches the app, navigates to the page with
docId/did test fixtures, performs UI actions to invoke mentions and task edits,
and asserts the expected DOM/state updates to prevent regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/electron/src/renderer/components/PageView.tsx`:
- Line 130: The forEach callback currently uses a concise arrow body
(presence.forEach((user) => addSuggestion(user))) which causes an implicit
return and triggers the Biome lint rule; change it to a block-bodied callback so
it does not return a value (e.g., presence.forEach((user) => {
addSuggestion(user); })) — update the occurrence in the PageView component where
presence.forEach is called and ensure addSuggestion is invoked inside a
block-bodied arrow function.

In `@apps/web/src/routes/doc`.$docId.tsx:
- Line 103: The forEach callback for presence.forEach currently uses a concise
arrow body which can implicitly return a value; change it to a block-bodied
callback so nothing is returned—replace presence.forEach((user) =>
addSuggestion(user)) with a block-bodied form that calls addSuggestion(user)
inside braces (e.g., presence.forEach((user) => { addSuggestion(user); })),
ensuring the callback has no return value and referencing the presence.forEach
and addSuggestion symbols.

---

Nitpick comments:
In `@apps/electron/src/renderer/components/PageView.tsx`:
- Around line 780-810: Add an Electron Playwright E2E path that exercises the
new integrated editor flow wired here: render the editor with
mentionSuggestions, trigger onPageTasksChange (handleTasksChange) and verify
renderTaskView via TaskCollectionEmbed props
(scope/assignee/dueDate/status/showHierarchy), ensure OrphanedThreadList appears
and can toggle/dismiss/reattach (onToggleCollapse/onDismiss/onReattach/onSelect)
and that PageTasksPanel (pageId prop) is visible and syncs with task changes;
create a new Playwright test under the Electron e2e suite that launches the app,
navigates to the page with docId/did test fixtures, performs UI actions to
invoke mentions and task edits, and asserts the expected DOM/state updates to
prevent regressions.

In `@packages/editor/src/components/TaskMentionMenu.test.tsx`:
- Line 1: This test module (TaskMentionMenu.test.tsx) is missing the required
file-level JSDoc banner; add the standard package-level JSDoc comment as the
very first lines of the file (before any imports) using the exact header string:
/** `@xnetjs/sync` - Unified sync primitives for xNet */ so the file begins with
that banner then the existing imports (e.g., render, screen from
'@testing-library/react').

In `@packages/editor/src/utils/gravatar.ts`:
- Around line 1-94: Add a file-level JSDoc header at the top of the file and
JSDoc blocks for the exported functions createGravatarUrl and md5 (including
`@param`, `@returns` and an `@example` for each) so the module follows package
documentation conventions; update the JSDoc to describe the module purpose
(Gravatar utility), document createGravatarUrl(input: string, size?: number) and
md5(input: string) with concise descriptions and usage examples, and ensure
exported symbols (createGravatarUrl, md5) are referenced in their respective
comments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 822b1213-ac75-4ff6-aae1-ad733b254d86

📥 Commits

Reviewing files that changed from the base of the PR and between 9247fe8 and 0c94dcc.

📒 Files selected for processing (8)
  • apps/electron/src/renderer/components/PageView.tsx
  • apps/web/src/routes/doc.$docId.tsx
  • packages/editor/src/components/TaskMentionMenu.test.tsx
  • packages/editor/src/components/TaskMentionMenu.tsx
  • packages/editor/src/extensions.ts
  • packages/editor/src/react.ts
  • packages/editor/src/utils/gravatar.test.ts
  • packages/editor/src/utils/gravatar.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/editor/src/utils/gravatar.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/editor/src/components/TaskMentionMenu.tsx
  • packages/editor/src/extensions.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/canvas/src/__tests__/edge-bundling.test.ts (1)

329-332: Consider separating the correctness assertion from the timed callback.

The expect(bundles.length).toBeGreaterThan(0) runs multiple times (once per sample plus warm-up). While the overhead is minimal and the approach works, separating the performance measurement from the correctness assertion would make the test intent clearer.

♻️ Optional refactor to separate concerns
-      const elapsed = getMedianDuration(() => {
-        const bundles = b.bundle(edges, positions)
-        expect(bundles.length).toBeGreaterThan(0)
-      })
+      let bundles: ReturnType<typeof b.bundle> = []
+      const elapsed = getMedianDuration(() => {
+        bundles = b.bundle(edges, positions)
+      })
+
+      expect(bundles.length).toBeGreaterThan(0)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/canvas/src/__tests__/edge-bundling.test.ts` around lines 329 - 332,
The test is asserting correctness inside the timed callback which causes the
expectation to run multiple times; change it so getMedianDuration(() =>
b.bundle(edges, positions)) only measures performance and does not include the
expect, then call b.bundle(edges, positions) once (or reuse the last result)
after timing and assert expect(bundles.length).toBeGreaterThan(0). Update
references to getMedianDuration, b.bundle and bundles accordingly so the timing
loop and correctness assertion are separate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/canvas/src/__tests__/minimap.test.ts`:
- Around line 11-13: The helper getPerformanceBudget lacks JSDoc with an
`@example` and uses a truthy check on process.env.CI; update getPerformanceBudget
to add JSDoc describing parameters/return and include an `@example` showing
typical usage, and change the CI check from process.env.CI to an explicit
comparison matching vitest.config.ts (e.g., process.env.CI === 'true') so values
like "false" aren't treated as truthy; apply the same explicit CI check pattern
to the other mentioned test helpers for consistency.

In `@packages/views/src/__tests__/virtualized-table.test.tsx`:
- Around line 20-22: Add a JSDoc block to the getPerformanceBudget function that
documents parameters, return value, and includes an `@example` showing typical
usage (e.g., passing local and CI budgets and the expected returned value
depending on process.env.CI). Ensure the comment sits immediately above the
getPerformanceBudget declaration and uses standard JSDoc tags like `@param` and
`@returns` plus an `@example` snippet demonstrating both CI and local outcomes.

---

Nitpick comments:
In `@packages/canvas/src/__tests__/edge-bundling.test.ts`:
- Around line 329-332: The test is asserting correctness inside the timed
callback which causes the expectation to run multiple times; change it so
getMedianDuration(() => b.bundle(edges, positions)) only measures performance
and does not include the expect, then call b.bundle(edges, positions) once (or
reuse the last result) after timing and assert
expect(bundles.length).toBeGreaterThan(0). Update references to
getMedianDuration, b.bundle and bundles accordingly so the timing loop and
correctness assertion are separate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 20473016-6a9d-4a80-8467-35cf98153265

📥 Commits

Reviewing files that changed from the base of the PR and between 0c94dcc and 29c110e.

📒 Files selected for processing (4)
  • packages/canvas/src/__tests__/edge-bundling.test.ts
  • packages/canvas/src/__tests__/minimap.test.ts
  • packages/views/src/__tests__/editor-performance.test.tsx
  • packages/views/src/__tests__/virtualized-table.test.tsx

Comment on lines +11 to +13
function getPerformanceBudget(localBudgetMs: number, ciBudgetMs: number): number {
return process.env.CI ? ciBudgetMs : localBudgetMs
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and inspect the relevant lines
head -n 20 packages/canvas/src/__tests__/minimap.test.ts

Repository: crs48/xNet

Length of output: 558


🏁 Script executed:

# Run the verification script from the review to check CI patterns
fd -t f -e yml -e yaml -e json -e ts -e tsx -e js -e jsx -e sh -0 \
  | xargs -0 rg -n --no-heading -e '(^|[[:space:]])CI[[:space:]]*[:=][[:space:]]*["'"'"'](false|0|no)["'"'"']?' -e 'process\.env\.CI' | head -50

Repository: crs48/xNet

Length of output: 659


🏁 Script executed:

# Check the full structure of the test file to understand context
wc -l packages/canvas/src/__tests__/minimap.test.ts

Repository: crs48/xNet

Length of output: 103


Add JSDoc with @example to the performance budget helper.

This function must include JSDoc documentation per the packages/**/*.{ts,tsx} guideline. Additionally, process.env.CI ? ... treats values like "false" as truthy; use explicit comparison matching the pattern already established in vitest.config.ts for safer CI detection.

Proposed fix
+/**
+ * Returns environment-aware performance budget for timing assertions.
+ *
+ * `@example`
+ * const budget = getPerformanceBudget(100, 200)
+ */
 function getPerformanceBudget(localBudgetMs: number, ciBudgetMs: number): number {
-  return process.env.CI ? ciBudgetMs : localBudgetMs
+  const isCi = process.env.CI === 'true'
+  return isCi ? ciBudgetMs : localBudgetMs
 }

Note: The same pattern exists in packages/views/src/__tests__/virtualized-table.test.tsx:21, packages/views/src/__tests__/editor-performance.test.tsx:7, and packages/canvas/src/__tests__/edge-bundling.test.ts:17 and should be updated for consistency with vitest.config.ts:5.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getPerformanceBudget(localBudgetMs: number, ciBudgetMs: number): number {
return process.env.CI ? ciBudgetMs : localBudgetMs
}
/**
* Returns environment-aware performance budget for timing assertions.
*
* `@example`
* const budget = getPerformanceBudget(100, 200)
*/
function getPerformanceBudget(localBudgetMs: number, ciBudgetMs: number): number {
const isCi = process.env.CI === 'true'
return isCi ? ciBudgetMs : localBudgetMs
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/canvas/src/__tests__/minimap.test.ts` around lines 11 - 13, The
helper getPerformanceBudget lacks JSDoc with an `@example` and uses a truthy check
on process.env.CI; update getPerformanceBudget to add JSDoc describing
parameters/return and include an `@example` showing typical usage, and change the
CI check from process.env.CI to an explicit comparison matching vitest.config.ts
(e.g., process.env.CI === 'true') so values like "false" aren't treated as
truthy; apply the same explicit CI check pattern to the other mentioned test
helpers for consistency.

Comment on lines +20 to +22
function getPerformanceBudget(localBudgetMs: number, ciBudgetMs: number): number {
return process.env.CI ? ciBudgetMs : localBudgetMs
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add JSDoc (with @example) for the new helper.

getPerformanceBudget is a new function in a packages/**/*.{ts,tsx} file and should include function-level JSDoc with an example.

Proposed update
+/**
+ * Returns a test performance budget adjusted for local vs CI environments.
+ *
+ * `@example`
+ * const budget = getPerformanceBudget(700, 1200)
+ */
 function getPerformanceBudget(localBudgetMs: number, ciBudgetMs: number): number {
   return process.env.CI ? ciBudgetMs : localBudgetMs
 }

As per coding guidelines: packages/**/*.{ts,tsx}: Function documentation should include JSDoc with @example tags.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getPerformanceBudget(localBudgetMs: number, ciBudgetMs: number): number {
return process.env.CI ? ciBudgetMs : localBudgetMs
}
/**
* Returns a test performance budget adjusted for local vs CI environments.
*
* `@example`
* const budget = getPerformanceBudget(700, 1200)
*/
function getPerformanceBudget(localBudgetMs: number, ciBudgetMs: number): number {
return process.env.CI ? ciBudgetMs : localBudgetMs
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/views/src/__tests__/virtualized-table.test.tsx` around lines 20 -
22, Add a JSDoc block to the getPerformanceBudget function that documents
parameters, return value, and includes an `@example` showing typical usage (e.g.,
passing local and CI budgets and the expected returned value depending on
process.env.CI). Ensure the comment sits immediately above the
getPerformanceBudget declaration and uses standard JSDoc tags like `@param` and
`@returns` plus an `@example` snippet demonstrating both CI and local outcomes.

- share page task panels and mention suggestion construction across web and desktop
- harden task sync, due date, and task view updates against stale or partial state
- align overdue logic and task tree depth handling with canonical task semantics
Comment thread packages/react/src/components/TaskCollectionEmbed.tsx
Comment on lines +73 to +79
function getTodayDateString(): string {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overdue indicator uses local time; data layer uses UTC

getTodayDateString() (lines 73–78) derives today's date from new Date() using local getters (getFullYear, getMonth, getDate), so the "today" string varies by the viewer's timezone. By contrast, useTasks.ts and TaskCollectionEmbed.tsx both call getStartOfUtcDay(Date.now()) for overdue comparisons.

This means the data-overdue attribute on the inline chip and the overdue indicator in the embed panel can disagree for dates near midnight UTC. A task due today in UTC may render as overdue in the chip for users in UTC+X timezones, while the embed panel still shows it as on-time.

Consider normalising to UTC in getTodayDateString:

Suggested change
function getTodayDateString(): string {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
function getTodayDateString(): string {
const now = new Date()
const year = now.getUTCFullYear()
const month = String(now.getUTCMonth() + 1).padStart(2, '0')
const day = String(now.getUTCDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}

Comment on lines +240 to +245
const handlePickDueDate = useCallback(async () => {
const selectedDate = await pickDate(getCurrentTaskDueDate(editor))
if (!selectedDate) return

editor.chain().focus().setTaskDueDate(selectedDate).run()
}, [editor])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No path to clear an existing due date via the toolbar

handlePickDueDate only calls setTaskDueDate when selectedDate is non-null; dismissing the native picker (without picking a date) does nothing. There is no affordance to remove a due date once set.

TaskDueDateExtension already exposes clearTaskDueDate, so the button could toggle: if a due date is already set, clicking the button should clear it instead of opening the picker.

Suggested change
const handlePickDueDate = useCallback(async () => {
const selectedDate = await pickDate(getCurrentTaskDueDate(editor))
if (!selectedDate) return
editor.chain().focus().setTaskDueDate(selectedDate).run()
}, [editor])
const handlePickDueDate = useCallback(async () => {
if (getCurrentTaskDueDate(editor) !== null) {
editor.chain().focus().clearTaskDueDate().run()
return
}
const selectedDate = await pickDate(null)
if (!selectedDate) return
editor.chain().focus().setTaskDueDate(selectedDate).run()
}, [editor])

Comment on lines +34 to +37
function getStartOfUtcDay(timestamp: number): number {
const date = new Date(timestamp)
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicated getStartOfUtcDay helper

An identical getStartOfUtcDay function is defined in both packages/react/src/components/TaskCollectionEmbed.tsx (line 34) and packages/react/src/hooks/useTasks.ts (line 87). Since both files live in the same @xnetjs/react package, extracting this to a small shared utility (e.g., src/utils/dateUtils.ts) would eliminate the duplication and keep the overdue logic in one place.

- merge origin/main into codex/tasks-page-task-sync
- preserve task-item toolbar visibility with derived selection state
- prevent TaskCollectionEmbed from falling back to an unscoped query without page context
- add regression coverage for missing page context handling
- carry forward upstream CI and editor e2e updates
@github-actions

github-actions Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Preview: https://xnet.fyi/pr/4/app/

github-actions Bot added a commit that referenced this pull request Mar 6, 2026
Comment on lines +21 to +28
function formatDueDate(timestamp: number | undefined): string | null {
if (typeof timestamp !== 'number') return null

return new Date(timestamp).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric'
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

formatDueDate shifts dates for users west of UTC

Timestamps are stored as UTC midnight (via Date.UTC(...) in usePageTaskSync). Converting with new Date(timestamp).toLocaleDateString() re-interprets that midnight instant in the viewer's local timezone. A user in UTC−8 sees midnight UTC as 4 PM the previous day, so a task due "Mar 20" is displayed as "Mar 19".

The inline chip in TaskDueDateExtension is rendered from the date string directly (new Date(year, month-1, day)) so it always shows the correct date. This creates a visible inconsistency: the panel label and the chip label disagree for the same task.

Fix by pinning the formatter to UTC:

Suggested change
function formatDueDate(timestamp: number | undefined): string | null {
if (typeof timestamp !== 'number') return null
return new Date(timestamp).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric'
})
}
function formatDueDate(timestamp: number | undefined): string | null {
if (typeof timestamp !== 'number') return null
return new Intl.DateTimeFormat(undefined, {
month: 'short',
day: 'numeric',
timeZone: 'UTC'
}).format(timestamp)
}

The same issue exists in packages/react/src/components/TaskCollectionEmbed.tsx at line 28.

Comment on lines +32 to +36
addSuggestion(did ? { did } : null, true)

for (const user of presence) {
addSuggestion(user)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Local user stub blocks their presence entry from providing a display name

addSuggestion({ did }, true) is called first, adding the current user with only a did field — no name, color, or avatar. The suggestions.has(entry.did) guard then prevents the richer presence entry (with name/color/avatar) from ever being used when the for...of presence loop runs.

As a result the local user's mention chip always shows a truncated DID (${did.slice(8, 16)}...) as its label, even when their full name is available in presence.

Fix: look up the presence entry for the local user first so that its name and avatar are used:

Suggested change
addSuggestion(did ? { did } : null, true)
for (const user of presence) {
addSuggestion(user)
}
const presenceArray = [...presence]
const localEntry = presenceArray.find((u) => u.did === did)
// Add local user first (prefer presence data if available)
addSuggestion(localEntry ?? (did ? { did } : null), true)
for (const user of presenceArray) {
addSuggestion(user)
}

@crs48
crs48 merged commit 8ab9a44 into main Mar 6, 2026
9 checks passed
@crs48
crs48 deleted the codex/tasks-page-task-sync branch March 6, 2026 07:16
crs48 added a commit that referenced this pull request Mar 7, 2026
feat(tasks): add inline assignees and due dates to page tasks
crs48 added a commit that referenced this pull request Mar 7, 2026
feat(tasks): add inline assignees and due dates to page tasks
crs48 added a commit that referenced this pull request Jun 28, 2026
Implements exploration **0244** — the fourth essay in the xNet blog
series (sea → soil → sky → **the wind**).

## What this is
A new blog post, *The Desert That Feeds the Forest*, that takes the
metaphors the user loved — **desert bees** and **the Sahara fertilizing
the Amazon** — and ties them to xNet, the open web, and Big Tech.

The spine: the important thing is almost never the thing on the surface.
A viral AI-clickbait video ("frozen bees in the Sahara") points,
accidentally, at two real and well-documented phenomena:
- The **dust bridge** — ~27.7M tons of Saharan dust a year carry ~22,000
tons of phosphorus to the Amazon, replacing almost exactly what the
rainforest loses to rain. The richest source is the Bodélé Depression:
fossil diatoms from a lake dead for millennia. *Death feeds life; the
wasteland is the supply line.*
- The **solitary desert bee** — an uncredited, invisible keystone
pollinator (not the branded honeybee).

…mapped onto the open web's invisible substrate (open protocols + the
maintainers Nadia Eghbal called the *"keystone species… invisible
because they work"*), with Big Tech as the monoculture plantation that
strip-mines the commons. xNet's line: **build like the dust, not the
plantation** — with Charter receipts (Own / Exit / Calm).

## Changes
- `site/src/pages/blog/the-desert-that-feeds-the-forest.astro` — the
essay
- `site/src/components/blog/DustHero.astro` — hero art (dust plume
arcing across an ocean; cosmic-X as a mid-Atlantic mote)
- `site/src/components/blog/DustBridge.astro` — the structural diagram
(Bodélé → Atlantic → Amazon → runoff, mapped to the web)
- `site/src/components/blog/HonestDesert.astro` — the self-audit panel
- `site/src/data/blog.ts` — metadata entry (reuses the `nature` tag;
index + RSS update automatically)
- changelog fragment + exploration doc checked off (`[_]`→`[x]`)

All art is inline SVG (no third-party assets — Self-Audit parity).

## Verification
- `pnpm --filter site build` ✅ (104 pages; the new page, index card, and
RSS entry all generated, newest-first)
- Rendered the built site and confirmed the hero, `DustBridge`, and
`HonestDesert` render with no console errors; OG title/description
populate.
- Prose honesty: the post never claims the "frozen bees" experiment was
real; figures are hedged ("roughly", "close to"); `HonestDesert`
discloses the simplifications.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants