Skip to content

foam-core: read tasks into Resource - #1734

Open
riccardoferretti wants to merge 3 commits into
mainfrom
feature/core-task-model
Open

riccardoferretti wants to merge 3 commits into
mainfrom
feature/core-task-model

Conversation

@riccardoferretti

@riccardoferretti riccardoferretti commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

@foam/core now reads GFM tasks into the parsed note as Resource.tasks, the same way it reads tags. Task has static functions that change a task's status and schedule it for a day by linking that day's note.

What a task is

A task is a list item that the parser (remark-parse 8 with GFM, as core runs it) reads with a checkbox: [ ], [x] or [X], then a space or a tab, then text on the same line. A tab between the brackets also reads as open. Tasks can be nested at any depth and can sit inside block quotes, and the parser already leaves out frontmatter, code and HTML blocks. - [ ] on its own is not a task.

API

Resource.tasks: Task[] Each task's range (from its checkbox to the end of its line), its status, and its text (what follows the checkbox on that line)
TaskStatus Open or Done, stored as the strings 'open' and 'done'
Task.createStatusEdit(task, status) The edit that changes the mark in the checkbox
Task.getDays(resource, task) The days the task links: wikilinks on its line, not embeds, whose target's last part is a real YYYY-MM-DD date
Task.createScheduleEdits(resource, task, from, to) Moves the task's link from one day to another (using MarkdownLink.createUpdateLinkEdit), or adds [[to]] after its text
Task.createRemoveDayEdits(resource, task, day) Removes the task's links to a day. Removing a link that scheduling added gives back the line exactly as it was

Also in this PR

  • Every place that builds a Resource now sets tasks: [], and the VS Code parser cache version goes from 7 to 8.
  • The parser reads [[2026-10-09# [[2026-10-09]] as a single link. A link like that, which runs across an unclosed [[, is not treated as a day link, so removing a day can't delete the text before it.

Why

The iPhone app (packages/foam-mobile, not on main yet) has its own task code. Once this is merged, the app will read tasks from Resource.tasks and edit them with these functions. Its live editor still has to find list items in text that hasn't been parsed yet, so it keeps its own line reader for that.

Nothing else in core uses tasks yet. A workspace-wide FoamTasks index, like FoamTags, can be added when something needs it.

Testing

yarn build, yarn test:unit in every package, and yarn lint all pass. Core's benchmarks, run against a baseline taken before this change, show nothing slower.

🤖 Generated with Claude Code

A task line is a GFM list item, at any depth and inside block quotes,
whose checkbox has text after it on the same line, read by the block
rules of core's parser (remark-parse 8 with GFM, frontmatter): code,
frontmatter, HTML blocks, tables and link definitions hold none.

Moved from the iPhone app's task model, where it was written to move
here unchanged; only its imports change. A randomized test checks it
against the parser on 3000 generated notes. Exported from the barrel:
reading task lines and list items, the day links a task carries, and
the edits that tick, schedule and unschedule a task.

The app's test that the model imports only @foam/core and its own
files stays behind: inside core that rule has no meaning, and the
portability gate already rejects Node built-ins reachable from the
barrel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread packages/foam-core/src/tasks/day-links.ts Fixed
Comment thread packages/foam-core/src/tasks/day-links.ts Fixed
Comment thread packages/foam-core/src/tasks/day-links.ts Fixed
Comment thread packages/foam-core/src/tasks/markdown-blocks.ts Fixed
Finding inline code and the whitespace that ends a line used regexes
that backtrack: a line of 100,000 backticks, or of tabs followed by a
word, took seconds (CodeQL: polynomial regular expression).

Inline code is now read by its backtick runs, as remark-parse 8 reads
it: a run closes only at a run of exactly as many backticks, and a run
nothing closes is read again from its next backtick. The regex let a
longer run close a shorter one, hiding a day link that the parser
shows. Trailing whitespace is found by walking back from the end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@riccardoferretti riccardoferretti left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

here are my comments. in general i feel we could use more the model and less string operations. also we can add tasks to the Resource object and follow the same pattern we have for e.g. tags.
Also use the same pattern for utility fns that we use for Tags, Links, Resource, ... with the static fns

Comment thread packages/foam-core/src/tasks/code-fence.ts Outdated
Comment thread packages/foam-core/src/tasks/day-links.ts Outdated
Comment thread packages/foam-core/src/tasks/day-links.ts Outdated
Comment thread packages/foam-core/src/tasks/frontmatter.ts Outdated
Comment thread packages/foam-core/src/tasks/markdown-blocks.ts Outdated
Comment thread packages/foam-core/src/tasks/task-lines.ts Outdated
Comment thread packages/foam-core/src/tasks/task-lines.ts Outdated
/** The text after the checkbox and the space or tab that follows it. */
text: string;
/** The days it links, as `YYYY-MM-DD`, each once, in the order written. */
days: string[];

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

why strings and not Dates? genuine question

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Deliberate, and unchanged. A day here is a calendar date, not a moment in time, and a Date is a moment. The same Date falls on different days in different time zones, and two Dates for the same day are not ===. Resources also go through JSON in the parser cache, where a Date comes back as a string anyway. YYYY-MM-DD is exactly what the link says and what daily notes are named, so it compares, sorts and is written back as it is.

🤖 Addressed by Claude Code

Comment thread packages/foam-core/test/parser-tasks.ts Outdated
Tasks are now part of the parsed note, like tags: a parser plugin adds
each list item with a GFM checkbox and text after it on its line to
`Resource.tasks`, with its range (checkbox to end of line), status and
text. The status is a `TaskStatus` enum, open or done for now.

`Task` follows the model's pattern of an interface with static
functions: `createStatusEdit`, `getDays`, `createScheduleEdits` and
`createRemoveDayEdits`. Days come from the note's links; moving a day
link goes through `MarkdownLink.createUpdateLinkEdit`.

The line scanner that re-implemented remark's block rules goes, with
its code-fence, frontmatter and day-link string helpers and the test
comparing it to the parser. A wikilink the parser reads over an
unclosed `[[` is not taken for a day link, so removing a day can't
remove the text before it.

The VS Code parser cache version goes to 8 for the new field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@riccardoferretti riccardoferretti changed the title foam-core: one rule for what a task line is foam-core: read tasks into Resource Oct 10, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants