Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zennotes/desktop",
"productName": "ZenNotes",
"version": "2.69.1",
"version": "2.70.0",
"description": "ZenNotes desktop shell",
"private": true,
"main": "./out/main/index.js",
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,15 +671,15 @@ const TOOLS: ToolDef[] = [
schema: {
name: 'list_tasks',
description:
'Return every task (markdown checkbox) in live notes with parsed metadata: due date, priority, @waiting, tags. Filter by status, priority, date range, tag, or folder.',
'Return every task (markdown checkbox) in live notes with parsed metadata: due date, priority, @waiting, tags, and status, the column a Kanban board grouped by status shows it in (a task note’s `status:`, a line’s `@status:`, or the frontmatter `status:` of its note). Filter by state, priority, date range, tag, or folder.',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['open', 'done', 'waiting', 'all'],
description:
'open = not done, cancelled or waiting (default). done = checked. waiting = has @waiting. all = everything.'
'The task’s state, not its status column. open = not done, cancelled or waiting (default). done = checked. waiting = has @waiting. all = everything.'
},
priority: {
type: 'string',
Expand Down
22 changes: 22 additions & 0 deletions apps/desktop/src/mcp/vault-ops.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
toggleTaskInBody
} from './vault-ops'
import { TASK_LINE_RE as SHARED_TASK_LINE_RE } from '@shared/tasklists'
import { parseTasksFromBody as parseAppTasks } from '@shared/tasks'

// Every note-shaped MCP result carries `link`, the zennotes:// deep link a
// model renders as a markdown link so the user can click from chat straight
Expand Down Expand Up @@ -113,6 +114,27 @@ describe('mcp task states', () => {
expect(tasks.find((t) => t.content === 'deeply quoted')?.inProgress).toBe(true)
})

// An assistant sees which column of a status board a task is in, as the app
// and `zn mcp` report it, while this grammar keeps the token in the content
// (ZenNotes/zennotes#941).
it('reports each task’s status column the way the app reads it', async () => {
await writeFile(path.join(root, 'inbox', 'Ship it.md'), '---\ntags: [task]\nstatus: Ready\n---\nBuild it.\n')
await writeFile(path.join(root, 'inbox', 'Bare task.md'), '---\ntags: [task]\n---\nNo status yet.\n')
const sprint = '---\nstatus: backlog\n---\n- [ ] inherits\n- [ ] overrides @status:Review @status:later\n- [x] done here\n'
await writeFile(path.join(root, 'inbox', 'Sprint.md'), sprint)
const tasks = await scanAllTasks(root)
const statusOf = (content: string): string | undefined => tasks.find((t) => t.content === content)?.status
expect(statusOf('Ship it')).toBe('ready')
expect(statusOf('Bare task')).toBe('open')
expect(statusOf('inherits')).toBe('backlog')
expect(statusOf('overrides @status:Review @status:later')).toBe('review')
expect(statusOf('done here')).toBe('backlog')
expect(statusOf('actually do it')).toBeUndefined()
expect(JSON.parse(JSON.stringify(tasks.find((t) => t.content === 'actually do it')))).not.toHaveProperty('status')
const app = parseAppTasks(sprint, { path: 'inbox/Sprint.md', title: 'Sprint', folder: 'inbox' })
expect(tasks.filter((t) => t.sourcePath === 'inbox/Sprint.md').map((t) => t.status)).toEqual(app.map((t) => t.status))
})

it('toggles an ordered or quoted task in place', () => {
const body = '1. [ ] first\n> - [ ] quoted\n'
expect(toggleTaskInBody(body, 0)).toBe('1. [x] first\n> - [ ] quoted\n')
Expand Down
21 changes: 19 additions & 2 deletions apps/desktop/src/mcp/vault-ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { retitleLeadingHeading } from '@shared/note-heading-sync'
import { normalizeNoteComments } from '@shared/note-comments'
import type { NoteComment, NoteCommentInput } from '@shared/ipc'
export type { NoteComment, NoteCommentInput }
import { noteTasksMode, type NoteTasksMode } from '@shared/tasks'
import { inlineTaskField, noteTasksMode, type NoteTasksMode } from '@shared/tasks'
import {
isPathExcludedFromTasks,
normalizeTasksExcludedFolders
Expand Down Expand Up @@ -283,6 +283,13 @@ export interface VaultTask {
due?: string
priority?: 'high' | 'med' | 'low'
waiting: boolean
/** The column the task sits in on a Kanban board grouped by status, read as
* the app reads it: a task note's frontmatter `status:` (`open` when it has
* none), a checkbox line's `@status:`, or else the frontmatter `status:` of
* the note the line is in. Lower-cased; absent when a line has none. The
* `@status:` token stays in `content`, as every field does in this grammar
* (ZenNotes/zennotes#941). */
status?: string
tags: string[]
/** How this task is stored. `'file'` is a whole-note task (TaskNotes-style: a
* `.md` file tagged `task`, metadata in frontmatter); `'inline'` (the default
Expand Down Expand Up @@ -1356,11 +1363,17 @@ function normalizeDueDate(raw: string | undefined): string | undefined {
function parseNoteDefaults(body: string): {
due?: string
priority?: 'high' | 'med' | 'low'
status?: string
tasksMode: NoteTasksMode
} {
const m = body.match(FRONTMATTER_RE)
if (!m) return { tasksMode: 'all' }
const out: { due?: string; priority?: 'high' | 'med' | 'low'; tasksMode: NoteTasksMode } = {
const out: {
due?: string
priority?: 'high' | 'med' | 'low'
status?: string
tasksMode: NoteTasksMode
} = {
tasksMode: 'all'
}
for (const rawLine of m[1].split('\n')) {
Expand All @@ -1377,6 +1390,8 @@ function parseNoteDefaults(body: string): {
else if (key === 'priority') {
const p = normalizePriority(value)
if (p) out.priority = p
} else if (key === 'status') {
if (value) out.status = value.toLowerCase()
} else if (key === 'tasks') out.tasksMode = noteTasksMode(value)
}
return out
Expand Down Expand Up @@ -1478,6 +1493,7 @@ function parseTasksFromBody(
due: due ?? defaults.due,
priority: priority ?? defaults.priority,
waiting,
status: inlineTaskField(tail, 'status') ?? defaults.status,
tags
})
taskIndex += 1
Expand Down Expand Up @@ -1603,6 +1619,7 @@ function parseTaskFile(
due: normalizeDueDate(firstScalar(fm.due)),
priority: normalizePriority(firstScalar(fm.priority)),
waiting: status === 'waiting',
status,
tags: tags.filter((t) => t !== TASK_FILE_TAG),
kind: 'file',
scheduled: normalizeDueDate(firstScalar(fm.scheduled)),
Expand Down
20 changes: 10 additions & 10 deletions apps/desktop/terminal-release.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
"release": {
"repository": "ZenNotes/tui",
"protocol": 1,
"version": "0.7.1",
"commit": "91bb33b42a3d156c2e577762b1f7ce1feb02d586",
"version": "0.8.0",
"commit": "58b0e5683bdc35049e7e592998a3f236093e7557",
"artifacts": {
"darwin-arm64": {
"url": "https://github.com/ZenNotes/tui/releases/download/v0.7.1/zn_0.7.1_darwin_arm64.tar.gz",
"sha256": "cb0c90410fcd86972b4e62a24840bd0f5d2d82d403cfc556399e3c735b43651c"
"url": "https://github.com/ZenNotes/tui/releases/download/v0.8.0/zn_0.8.0_darwin_arm64.tar.gz",
"sha256": "29ebd83447c45a8fd5f8196f2c6680521bc1a2bbbec6afa609fadec480006042"
},
"darwin-x64": {
"url": "https://github.com/ZenNotes/tui/releases/download/v0.7.1/zn_0.7.1_darwin_amd64.tar.gz",
"sha256": "c6df5efe8d18dfa598e1ce8698fdd1a2eccf76892d94533479838cdd9a3e5d37"
"url": "https://github.com/ZenNotes/tui/releases/download/v0.8.0/zn_0.8.0_darwin_amd64.tar.gz",
"sha256": "7d6e91766664d72bb938e20644ab2501d8691aba4ae82168f7d18741850ef22e"
},
"linux-arm64": {
"url": "https://github.com/ZenNotes/tui/releases/download/v0.7.1/zn_0.7.1_linux_arm64.tar.gz",
"sha256": "1151c6daed55825e881014873802c32c056dff68797e64ed83451b9fdfe3f0b9"
"url": "https://github.com/ZenNotes/tui/releases/download/v0.8.0/zn_0.8.0_linux_arm64.tar.gz",
"sha256": "b73d9356b65a597364ba09bcb086be1b37b24c7c4cd2e806e990b00e8a997f5f"
},
"linux-x64": {
"url": "https://github.com/ZenNotes/tui/releases/download/v0.7.1/zn_0.7.1_linux_amd64.tar.gz",
"sha256": "b61d145931c32b417eaf5f8bce379e7f94e327e3b5527f7868dcba3a3a178ac2"
"url": "https://github.com/ZenNotes/tui/releases/download/v0.8.0/zn_0.8.0_linux_amd64.tar.gz",
"sha256": "5f5484434dda2c4745529f4c54c955c678219ce05880e6ff3c25a5af8845b2b1"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion apps/quicklook/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zennotes/quicklook",
"private": true,
"version": "2.69.1",
"version": "2.70.0",
"type": "module",
"description": "The macOS Quick Look preview for Markdown files: the page it renders and the Swift extension that serves it",
"homepage": "https://zennotes.org",
Expand Down
2 changes: 1 addition & 1 deletion apps/share-viewer/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zennotes/share-viewer",
"private": true,
"version": "2.69.1",
"version": "2.70.0",
"type": "module",
"description": "Read-only renderer for publicly shared ZenNotes, embedded by the zennotes.org website",
"homepage": "https://zennotes.org",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zennotes/web",
"private": true,
"version": "2.69.1",
"version": "2.70.0",
"type": "module",
"description": "ZenNotes web client for self-hosted and hosted deployments",
"homepage": "https://zennotes.org",
Expand Down
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zennotes-monorepo",
"private": true,
"version": "2.69.1",
"version": "2.70.0",
"description": "ZenNotes monorepo for desktop, web, and self-hosted server builds",
"packageManager": "npm@10.9.2",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/app-core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zennotes/app-core",
"private": true,
"version": "2.69.1",
"version": "2.70.0",
"type": "module",
"exports": {
"./main": "./src/main.tsx",
Expand Down
20 changes: 6 additions & 14 deletions packages/app-core/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1029,20 +1029,12 @@ function App(): JSX.Element {
!!document.querySelector('[data-prompt-modal]') ||
!!document.querySelector('[data-confirm-modal]') ||
!!document.querySelector('[data-cloud-conflict-dialog]')
// Search Notes is a toggle: its own shortcut closes the palette it
// opened (#510 moved it here from the bubble handler, which had no
// overlay guard at all). A confirm on top of the palette, such as the
// Ctrl+D trash confirmation, keeps the key to itself.
if (
state.searchOpen &&
!modalOrMenuOpen &&
matchesShortcut(e, overrides, 'global.searchNotes')
) {
e.preventDefault()
e.stopImmediatePropagation()
setSearchOpen(false)
return
}
// `global.searchNotes` is deliberately not a toggle. `Mod+P` resolves to
// `Ctrl+P` on Linux and Windows, which is also the Emacs previous-item
// chord every palette handles via `isPalettePreviousKey()`. A toggle here
// ran capture-phase and called `stopImmediatePropagation()`, making that
// branch unreachable while the palette was open (#938). Escape dismisses.
// Do not reinstate a toggle above the overlay guard.
if (
state.settingsOpen ||
state.whatsNewOpen ||
Expand Down
Loading
Loading