feat(tasks): due dates + multi-trigger task autocomplete (0172) - #60
Conversation
Consolidate the three timezone-safe ms<->YYYY-MM-DD conversions that had been reimplemented across TaskDetailForm, the projection sync, and the app projection into one canonical module in @xnetjs/ui. The code was already UTC-correct everywhere; this removes the drift risk and locks the all-day invariant with cross-timezone tests (UTC / LA / Tokyo / Kiritimati). The sync layer keeps a local mirror (toDateTimestamp) so it need not depend on the UI kit; both copies are pinned by tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e 1) Add an in-house, dependency-free NL date parser (today/tomorrow, weekdays, "in N days", ISO, M/D, month names) and a "type a date…" field in the due-date popover with a live preview and Enter-to-commit, alongside a new "This weekend" preset. Trailing-detection helper (for inline title use) is exported but not yet wired into a surface. Parser resolves everything to the canonical UTC-midnight day and is fully unit-tested against a fixed reference date. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generalize MentionTextInput from a single @-trigger into a multi-trigger composer: @name assigns, #tag categorizes (create-on-the-fly), and a trailing date phrase ("ship it friday") surfaces a confirm-to-commit due suggestion — each strips its token and sets the structured field, so a plain title drives the task's relations. Wire the new triggers into the Tasks quick-add (pending due/tag chips) and the TaskDetailForm title. Add a keyboard-first TaskDueDatePalette opened by `d` on a focused row, reusing the NL parser. All existing @-mention behavior and the comments MentionTextArea are preserved. Wiki links stay scoped to the rich description (titles render verbatim); the database-cell composer upgrade is left as a follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add DOM tests for the due-date palette (NL parse, presets, arrow-nav, clear, escape) so the fallow CRAP gate passes with coverage — the audit reports 0 introduced complexity/dead-code/duplication findings. Update the exploration checklist to reflect what shipped and what is deferred (database-cell composer, page-editor inline NL detection, [[ wikilinks). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
📝 WalkthroughWalkthroughThis PR adds due-date support and rich inline editing (tags and due dates) across task surfaces. It introduces canonical UTC-midnight date conversions, natural-language parsing, expands MentionTextInput for multi-trigger support, and integrates a new TaskDueDatePalette into TasksView's focused-task and quick-add workflows. ChangesTask Due Dates and Rich Inline Editing
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Preview removed for PR #60. |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/components/TasksView.tsx (1)
338-345:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPrevent async tag-creation bleed-through across quick-add submits.
The async
onCreateTagcallback can resolve after a submit/reset, causing a stale tag chip to appear on the next draft and dropping the intended tag from the just-created task. Also, both tag add paths should dedupe IDs before appending.Suggested fix pattern
const quickAddRef = useRef<HTMLInputElement>(null) + const draftEpochRef = useRef(0) const handleCreate = async () => { const title = draft.trim() if (!title) return + draftEpochRef.current += 1 setDraft('') setDraftAssignees([]) setDraftTags([]) setDraftDue(null) @@ tags={tagOptions.filter((tag) => !draftTags.includes(tag.id))} - onTag={(tagId) => setDraftTags((current) => [...current, tagId])} + onTag={(tagId) => + setDraftTags((current) => (current.includes(tagId) ? current : [...current, tagId])) + } onCreateTag={(name) => { - void getOrCreateTag(name).then((tag) => { - if (tag) setDraftTags((current) => [...current, tag.id]) + const epoch = draftEpochRef.current + void getOrCreateTag(name).then((tag) => { + if (!tag || epoch !== draftEpochRef.current) return + setDraftTags((current) => + current.includes(tag.id) ? current : [...current, tag.id] + ) }) }}Also applies to: 452-456
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/components/TasksView.tsx` around lines 338 - 345, The quick-add suffers from async tag-creation bleed: when handleCreate resets draft state immediately, a later-resolving onCreateTag can append a stale tag to the next draft and drop the tag from the created task. Modify handleCreate and the other tag-add path to (1) use a local generation token / cancelled flag captured before calling onCreateTag so any resolved tag added after reset is discarded, and (2) always dedupe tag IDs when merging into draftTags (and when appending to the created task) so IDs aren't duplicated; refer to handleCreate, onCreateTag, draftTags, setDraftTags (and the other tag-add handler around lines 452-456) to implement the token/flag and ID dedupe before setDraftTags/setTaskTags.
🧹 Nitpick comments (2)
docs/explorations/0172_[_]_TASK_DUE_DATES_AND_RICH_INLINE_EDITING.md (1)
584-646: ⚡ Quick winClarify the status of unchecked checklist items (deferred vs pending).
Lines 602, 618, 643–645 are marked unchecked, but context clues suggest they are intentionally deferred:
- Line 602: Wire NL detection into page editor (appears to be a follow-up)
- Line 618: Upgrade database cell (marked with "(deferred…)")
- Lines 643, 645: Manual validation steps (live browser pass + wikilinks in editor)
Consider adding explicit "Deferred" or "Follow-up" badges to clarify that these are not blockers for this PR, rather than leaving them as bare unchecked boxes that might read as incomplete. This would help reviewers and future readers understand the intended scope.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/explorations/0172_`[_]_TASK_DUE_DATES_AND_RICH_INLINE_EDITING.md around lines 584 - 646, Update the three unchecked checklist items that are intentionally out-of-scope by annotating them as deferred/follow-up rather than leaving bare unchecked boxes: change the "Wire NL detection into the page editor's taskDueDate flow" item to explicitly read "(Deferred / follow-up)", change the "Upgrade the database task-cell quick-add to the multi-trigger composer (deferred — needs provider threading into the lower-level `views` package)" item to be marked "(Deferred)" (or convert its checkbox to a "Deferred" badge), and annotate the two manual validation steps ("Live browser pass: quick-add a task..." and "`[[` wikilinks + inline NL date detection...") to show they are follow-ups (e.g., "[ ] (Follow-up) Live browser pass..." and "[ ] (Follow-up) `[[` wikilinks...") so reviewers know these are not blockers for this PR.packages/ui/src/composed/tasks/task-editing.test.tsx (1)
142-188: ⚡ Quick winAdd a regression test for
onTag-only mode (noonCreateTag).Please add a case asserting that unknown
#querydoes not show/create a “Create …” option when creation callback is absent.Suggested test
+ it('does not offer create when onCreateTag is not provided', () => { + const onTag = vi.fn() + render(<MultiTriggerHarness onTag={onTag} />) + const input = screen.getByTestId('mt') as HTMLInputElement + + fireEvent.change(input, { target: { value: 'Plan `#roadmap`' } }) + expect(screen.queryByText(/Create/i)).toBeNull() + })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/composed/tasks/task-editing.test.tsx` around lines 142 - 188, Add a regression test to verify that when MultiTriggerHarness is rendered with only an onTag handler (no onCreateTag), unknown “#query” suggestions do not offer a "Create ..." option: render(<MultiTriggerHarness onTag={vi.fn()} />), change the input value to include an unknown hashtag (e.g., fireEvent.change(input, { target: { value: 'Plan `#roadmap`' } })), then assert that screen.queryByText(/Create/) is null (and optionally that no create callback was called) to ensure the create option is not shown when onCreateTag is absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/src/components/TaskDueDatePalette.tsx`:
- Around line 87-97: The ArrowDown branch can set activeIndex to -1 when
options.length is 0; fix by computing a safe maxIndex = Math.max(0,
options.length - 1) and use that when updating via setActiveIndex in the
onKeyDown handler (replace the current Math.min(index + 1, options.length - 1)
logic), keep the ArrowUp clamp as Math.max(index - 1, 0), and also guard the
Enter branch (commit) to only call commit(options[activeIndex]) when
options.length > 0 to avoid indexing into an empty array; reference: onKeyDown,
setActiveIndex, options, activeIndex, commit.
In `@packages/ui/src/composed/tasks/MentionTextInput.tsx`:
- Around line 205-223: The tags menu shows a "Create …" entry even when no
onCreateTag handler is provided, which lets users pick it and lose input; update
the logic that builds listItems (and the equivalent block around the other
occurrence) so the create option is only included when onCreateTag is truthy
(i.e., guard the branch that appends { id: '', name: menu.create } with a check
for onCreateTag or tagsEnabled tied to onCreateTag), ensuring selection of the
create entry only appears and can be acted on when onCreateTag is available;
adjust the useMemo/menu->listItems flow that references menu.kind === 'tags' and
menu.create to conditionally include the create item.
In `@packages/ui/src/composed/tasks/parse-due-date.ts`:
- Around line 225-233: The normalize function currently strips prefixes using a
regex that matches "due" before multi-word prefixes, causing inputs like "due by
friday" to be mis-normalized; update the regex in normalize to list multi-word
prefixes first (e.g., "due by", "due on") before single-word ones and include
all intended variants ("by", "on", "due") so the replace call removes the
longest applicable prefix via something like /^(due by|due on|due|by|on)\s+/
(function: normalize).
---
Outside diff comments:
In `@apps/web/src/components/TasksView.tsx`:
- Around line 338-345: The quick-add suffers from async tag-creation bleed: when
handleCreate resets draft state immediately, a later-resolving onCreateTag can
append a stale tag to the next draft and drop the tag from the created task.
Modify handleCreate and the other tag-add path to (1) use a local generation
token / cancelled flag captured before calling onCreateTag so any resolved tag
added after reset is discarded, and (2) always dedupe tag IDs when merging into
draftTags (and when appending to the created task) so IDs aren't duplicated;
refer to handleCreate, onCreateTag, draftTags, setDraftTags (and the other
tag-add handler around lines 452-456) to implement the token/flag and ID dedupe
before setDraftTags/setTaskTags.
---
Nitpick comments:
In `@docs/explorations/0172_`[_]_TASK_DUE_DATES_AND_RICH_INLINE_EDITING.md:
- Around line 584-646: Update the three unchecked checklist items that are
intentionally out-of-scope by annotating them as deferred/follow-up rather than
leaving bare unchecked boxes: change the "Wire NL detection into the page
editor's taskDueDate flow" item to explicitly read "(Deferred / follow-up)",
change the "Upgrade the database task-cell quick-add to the multi-trigger
composer (deferred — needs provider threading into the lower-level `views`
package)" item to be marked "(Deferred)" (or convert its checkbox to a
"Deferred" badge), and annotate the two manual validation steps ("Live browser
pass: quick-add a task..." and "`[[` wikilinks + inline NL date detection...")
to show they are follow-ups (e.g., "[ ] (Follow-up) Live browser pass..." and "[
] (Follow-up) `[[` wikilinks...") so reviewers know these are not blockers for
this PR.
In `@packages/ui/src/composed/tasks/task-editing.test.tsx`:
- Around line 142-188: Add a regression test to verify that when
MultiTriggerHarness is rendered with only an onTag handler (no onCreateTag),
unknown “#query” suggestions do not offer a "Create ..." option:
render(<MultiTriggerHarness onTag={vi.fn()} />), change the input value to
include an unknown hashtag (e.g., fireEvent.change(input, { target: { value:
'Plan `#roadmap`' } })), then assert that screen.queryByText(/Create/) is null
(and optionally that no create callback was called) to ensure the create option
is not shown when onCreateTag is absent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 63f351b4-9ccd-4f72-9501-c79949deb296
📒 Files selected for processing (15)
apps/web/src/components/TaskDueDatePalette.test.tsxapps/web/src/components/TaskDueDatePalette.tsxapps/web/src/components/TasksView.tsxapps/web/src/components/task-node-projection.tsdocs/explorations/0172_[_]_TASK_DUE_DATES_AND_RICH_INLINE_EDITING.mdpackages/react/src/hooks/useTaskProjectionSync.tspackages/ui/src/composed/tasks/MentionTextInput.tsxpackages/ui/src/composed/tasks/TaskDetailForm.tsxpackages/ui/src/composed/tasks/due-date.test.tspackages/ui/src/composed/tasks/due-date.tspackages/ui/src/composed/tasks/index.tspackages/ui/src/composed/tasks/parse-due-date.test.tspackages/ui/src/composed/tasks/parse-due-date.tspackages/ui/src/composed/tasks/task-editing.test.tsxpackages/ui/src/index.ts
| onKeyDown={(event) => { | ||
| if (event.key === 'ArrowDown') { | ||
| event.preventDefault() | ||
| setActiveIndex((index) => Math.min(index + 1, options.length - 1)) | ||
| } else if (event.key === 'ArrowUp') { | ||
| event.preventDefault() | ||
| setActiveIndex((index) => Math.max(index - 1, 0)) | ||
| } else if (event.key === 'Enter') { | ||
| event.preventDefault() | ||
| commit(options[activeIndex]) | ||
| } else if (event.key === 'Escape') { |
There was a problem hiding this comment.
Clamp keyboard index when the options list is empty.
With zero options, ArrowDown currently drives activeIndex to -1. Keep the index at 0 when the list is empty so keyboard state stays valid.
Suggested fix
onKeyDown={(event) => {
if (event.key === 'ArrowDown') {
event.preventDefault()
- setActiveIndex((index) => Math.min(index + 1, options.length - 1))
+ setActiveIndex((index) =>
+ options.length > 0 ? Math.min(index + 1, options.length - 1) : 0
+ )
} else if (event.key === 'ArrowUp') {
event.preventDefault()
- setActiveIndex((index) => Math.max(index - 1, 0))
+ setActiveIndex((index) => (options.length > 0 ? Math.max(index - 1, 0) : 0))
} else if (event.key === 'Enter') {📝 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.
| onKeyDown={(event) => { | |
| if (event.key === 'ArrowDown') { | |
| event.preventDefault() | |
| setActiveIndex((index) => Math.min(index + 1, options.length - 1)) | |
| } else if (event.key === 'ArrowUp') { | |
| event.preventDefault() | |
| setActiveIndex((index) => Math.max(index - 1, 0)) | |
| } else if (event.key === 'Enter') { | |
| event.preventDefault() | |
| commit(options[activeIndex]) | |
| } else if (event.key === 'Escape') { | |
| onKeyDown={(event) => { | |
| if (event.key === 'ArrowDown') { | |
| event.preventDefault() | |
| setActiveIndex((index) => | |
| options.length > 0 ? Math.min(index + 1, options.length - 1) : 0 | |
| ) | |
| } else if (event.key === 'ArrowUp') { | |
| event.preventDefault() | |
| setActiveIndex((index) => (options.length > 0 ? Math.max(index - 1, 0) : 0)) | |
| } else if (event.key === 'Enter') { | |
| event.preventDefault() | |
| commit(options[activeIndex]) | |
| } else if (event.key === 'Escape') { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/components/TaskDueDatePalette.tsx` around lines 87 - 97, The
ArrowDown branch can set activeIndex to -1 when options.length is 0; fix by
computing a safe maxIndex = Math.max(0, options.length - 1) and use that when
updating via setActiveIndex in the onKeyDown handler (replace the current
Math.min(index + 1, options.length - 1) logic), keep the ArrowUp clamp as
Math.max(index - 1, 0), and also guard the Enter branch (commit) to only call
commit(options[activeIndex]) when options.length > 0 to avoid indexing into an
empty array; reference: onKeyDown, setActiveIndex, options, activeIndex, commit.
| const tagsEnabled = Boolean(onTag || onCreateTag) | ||
| const dueEnabled = Boolean(onDueDate) | ||
|
|
||
| const menu = useMemo( | ||
| () => | ||
| dismissed | ||
| ? null | ||
| : buildMenu({ value, caret, people, peopleEnabled, tags, tagsEnabled, dueEnabled }), | ||
| [value, caret, people, peopleEnabled, tags, tagsEnabled, dueEnabled, dismissed] | ||
| ) | ||
| const menuOpen = mention !== null && people.length > 0 | ||
|
|
||
| const syncMention = (nextValue: string, caret: number | null) => { | ||
| setMention(caret == null ? null : findActiveMention(nextValue, caret)) | ||
| const listItems = | ||
| menu?.kind === 'people' | ||
| ? menu.items | ||
| : menu?.kind === 'tags' | ||
| ? menu.create | ||
| ? [...menu.items, { id: '', name: menu.create }] | ||
| : menu.items | ||
| : [] |
There was a problem hiding this comment.
Gate tag-create entries on onCreateTag availability.
The tags menu currently offers a “Create …” choice even when creation is not supported. Selecting it strips the token but invokes no callback, which drops user input with no state update.
Proposed fix
- const tagsEnabled = Boolean(onTag || onCreateTag)
+ const canCreateTags = Boolean(onCreateTag)
+ const tagsEnabled = Boolean(onTag) || canCreateTags
@@
- const listItems =
+ const listItems =
menu?.kind === 'people'
? menu.items
: menu?.kind === 'tags'
- ? menu.create
+ ? menu.create && canCreateTags
? [...menu.items, { id: '', name: menu.create }]
: menu.items
: []
@@
- const items = menu.create ? [...menu.items, { id: '', name: menu.create }] : menu.items
+ const items =
+ menu.create && canCreateTags ? [...menu.items, { id: '', name: menu.create }] : menu.itemsAlso applies to: 248-254
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/composed/tasks/MentionTextInput.tsx` around lines 205 - 223,
The tags menu shows a "Create …" entry even when no onCreateTag handler is
provided, which lets users pick it and lose input; update the logic that builds
listItems (and the equivalent block around the other occurrence) so the create
option is only included when onCreateTag is truthy (i.e., guard the branch that
appends { id: '', name: menu.create } with a check for onCreateTag or
tagsEnabled tied to onCreateTag), ensuring selection of the create entry only
appears and can be acted on when onCreateTag is available; adjust the
useMemo/menu->listItems flow that references menu.kind === 'tags' and
menu.create to conditionally include the create item.
| /** Normalize whitespace and case; strip a trailing/leading "due"/"by"/"on". */ | ||
| function normalize(input: string): string { | ||
| return input | ||
| .trim() | ||
| .toLowerCase() | ||
| .replace(/\s+/g, ' ') | ||
| .replace(/^(due|by|due by|on)\s+/, '') | ||
| .trim() | ||
| } |
There was a problem hiding this comment.
Handle multi-word prefixes correctly in normalization (due by / due on).
The current prefix stripping matches due before due by, so phrases like due by friday normalize incorrectly and fail full-phrase parsing. In trailing-mode flows, this can fall back to a shorter match and leave residual text in the title.
Proposed fix
function normalize(input: string): string {
- return input
+ let phrase = input
.trim()
.toLowerCase()
.replace(/\s+/g, ' ')
- .replace(/^(due|by|due by|on)\s+/, '')
- .trim()
+ .trim()
+
+ // Strip stacked leading helpers like "due by", "due on", "by", "on".
+ // Repeat so "due by friday" and "due on 6/20" both normalize correctly.
+ while (true) {
+ const next = phrase.replace(/^(?:due|by|on)\s+/, '').trim()
+ if (next === phrase) break
+ phrase = next
+ }
+
+ return phrase
}📝 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.
| /** Normalize whitespace and case; strip a trailing/leading "due"/"by"/"on". */ | |
| function normalize(input: string): string { | |
| return input | |
| .trim() | |
| .toLowerCase() | |
| .replace(/\s+/g, ' ') | |
| .replace(/^(due|by|due by|on)\s+/, '') | |
| .trim() | |
| } | |
| /** Normalize whitespace and case; strip a trailing/leading "due"/"by"/"on". */ | |
| function normalize(input: string): string { | |
| let phrase = input | |
| .trim() | |
| .toLowerCase() | |
| .replace(/\s+/g, ' ') | |
| .trim() | |
| // Strip stacked leading helpers like "due by", "due on", "by", "on". | |
| // Repeat so "due by friday" and "due on 6/20" both normalize correctly. | |
| while (true) { | |
| const next = phrase.replace(/^(?:due|by|on)\s+/, '').trim() | |
| if (next === phrase) break | |
| phrase = next | |
| } | |
| return phrase | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/composed/tasks/parse-due-date.ts` around lines 225 - 233, The
normalize function currently strips prefixes using a regex that matches "due"
before multi-word prefixes, causing inputs like "due by friday" to be
mis-normalized; update the regex in normalize to list multi-word prefixes first
(e.g., "due by", "due on") before single-word ones and include all intended
variants ("by", "on", "due") so the replace call removes the longest applicable
prefix via something like /^(due by|due on|due|by|on)\s+/ (function: normalize).
Implements exploration 0172: make due dates as easy to add as
@-mentioning an assignee, and bring@/#/date autocomplete to the plain task inputs.What shipped
Phase 0 — canonical dates. One timezone-safe
ms ⇄ YYYY-MM-DDmodule (due-date.ts) replaces three hand-rolled copies (form, projection sync, app projection). The code was already UTC-correct everywhere, so this is consolidation + a cross-timezone regression test (UTC/LA/Tokyo/Kiritimati), not a bugfix. The sync layer keeps a local mirror so it needn't depend on the UI kit; both are pinned by tests.Phase 1 — natural-language date entry. A small, dependency-free parser (
parse-due-date.ts) handlestoday/tomorrow, weekdays,in N days, ISO,M/D, and month names → resolved to a UTC-midnight day. The due-date popover gets a "type a date…" field with a live preview, plus a "This weekend" preset.Phase 2 — multi-trigger composer.
MentionTextInputis generalized from a single@trigger into@(assign),#(tag, create-on-the-fly), and a trailing date phrase → confirm-to-commit due suggestion. Each strips its token and sets the structured field, so a plain title drives the task's relations. Wired into the Tasks quick-add (pending due/tag chips) and theTaskDetailFormtitle. All prior@-mention behavior and the commentsMentionTextAreaare untouched.Phase 3 — keyboard + parity. A
dshortcut on a focused row opens a keyboard-firstTaskDueDatePalette(NL field + presets), alongsides/p/x.Deliberate deviations (see doc "Implementation Notes")
chrono-node— the repo ships zero date libs and keeps bundles flat (0171); the stableparseDueDateinterface lets chrono drop in later for locales/recurrence.packages/typeahead(C2) — smaller blast radius; C2 remains the long-term home.[[wikilinks scoped to the rich description — task titles render verbatim, so inert[[ ]]text doesn't belong there. The database-cell composer upgrade is also deferred (needs provider threading into the lower-levelviewspackage).Testing
due-date,parse-due-date, the multi-trigger composer (task-editing), andTaskDueDatePalette.tsc --noEmitclean for@xnetjs/uiandxnet-web; react sync + editor page-task suites green.fallow audit --coverage … --fail-on-issuespasses: 0 introduced complexity / dead-code / duplication findings.#urgentoffers create;dopens the palette ("in 2 weeks" → Sat Jun 27); no console errors.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
@mentions,#tags, and due date phrasesDocumentation