Skip to content

backlog

Go
import "github.com/andreoliwa/logseq-doctor/internal/backlog"

Index

Constants

Section values for the PocketBase `section` field. Ranked=1, Unranked=2, Orphan=3 so that (backlog_index, section, rank) sorts ranked tasks before unranked tasks before orphans within any backlog.

Go
const (
    SectionRanked   = 1 // manually ordered, above the ⤵️ Unranked tasks divider
    SectionUnranked = 2 // under ⤵️ Unranked tasks, 📅 Overdue tasks, ⏰ Scheduled tasks, ✨ New tasks, 🏷️ Triaged tasks
    SectionOrphan   = 3 // not referenced in any backlog page
)

quickCapturePageName is the Logseq page linked in newly created section dividers so the user can identify dividers inserted by lqd backlog.

Go
const quickCapturePageName = "quick capture"

Variables

Backlog section header definitions. Detection uses Header.Matches (case-insensitive label search). Creation uses Header.String() so the canonical emoji+label+tasks is always written.

Go
var (
    HeaderFocus     = Header{"🎯", "Focus"}
    HeaderOverdue   = Header{"📅", "Overdue"}
    HeaderNewTasks  = Header{"✨", "New"}
    HeaderTriaged   = Header{"🏷️", "Triaged"}
    HeaderScheduled = Header{"⏰", "Scheduled"}
    HeaderUnranked  = Header{"⤵️", "Unranked"}
)

allHeaders is the full list used to normalize section dividers on write-back.

Go
var allHeaders = []Header{
    HeaderFocus, HeaderOverdue, HeaderNewTasks,
    HeaderTriaged, HeaderScheduled, HeaderUnranked,
}

focusSectionHeaders are the section dividers on the Focus page. Used to find the insertion point for new block refs.

Go
var focusSectionHeaders = []Header{HeaderOverdue, HeaderNewTasks, HeaderTriaged, HeaderScheduled}

regularAreaSectionHeaders are section headers that mark the boundary of the regular area. A top-level block matching any of these is a section divider, not part of the regular area.

Go
var regularAreaSectionHeaders = []Header{
    HeaderFocus, HeaderOverdue, HeaderNewTasks, HeaderTriaged, HeaderScheduled, HeaderUnranked,
}

func AddBlockRefToFocusPage

Go
func AddBlockRefToFocusPage(transaction *logseq.Transaction, focusPageTitle, uuid string) error

AddBlockRefToFocusPage adds a block ref ((uuid)) to the Focus page.

func BlockRefExistsUnder

Go
func BlockRefExistsUnder(parent *content.Block, uuid logseqapi.TaskUUID) bool

BlockRefExistsUnder returns true if a block ref with the given UUID exists anywhere in the descendant tree of parent.

func FindFirstSectionDivider

Go
func FindFirstSectionDivider(page logseq.Page) *content.Block

FindFirstSectionDivider finds the first block whose text matches a known section header.

func FormatCount

Go
func FormatCount(count int, singular, plural string) string

FormatCount returns a string with the count and the singular or plural form of a word.

func MoveBlockRefToTriagedSection

Go
func MoveBlockRefToTriagedSection(transaction *logseq.Transaction, backlogPage string, uuid logseqapi.TaskUUID, triagedText, scheduledText string) error

MoveBlockRefToTriagedSection moves a block ref to the Triaged section of a backlog page. If the ref exists in the regular area (not under Focus, New tasks, Overdue, or Scheduled), it is removed from there. If it's already in Triaged, no duplicate is added. Creates the Triaged section if it doesn't exist.

func NormalizeHeaderText

Go
func NormalizeHeaderText(page logseq.Page) bool

NormalizeHeaderText scans all top-level blocks on the page and normalizes any block whose text node contains a known header keyword:

  • fixes the text to the canonical "emoji Label tasks" form
  • upgrades a plain Paragraph container to a Heading level 1

Returns true if any block was changed.

func RemoveBlockRefFromRegularArea

Go
func RemoveBlockRefFromRegularArea(page logseq.Page, uuid logseqapi.TaskUUID)

RemoveBlockRefFromRegularArea removes the block ref with the given UUID from the regular area of the page. The regular area is any top-level block ref that is not itself a section divider. Section dividers (Focus, Overdue, New tasks, Triaged, Scheduled, Unranked) and their children are not part of the regular area. Since we walk only top-level blocks, child refs are never seen.

func addTasksToCategories

Go
func addTasksToCategories(jsonTasks []logseqapi.TaskJSON, tasks *logseqapi.CategorizedTasks, currentTime func() time.Time)

addTasksToCategories adds tasks to the appropriate categories in CategorizedTasks.

func blockRefsFromPages

Go
func blockRefsFromPages(page logseq.Page) *set.Set[string]

func collectTriagedRefs

Go
func collectTriagedRefs(page logseq.Page, state *pageState)

collectTriagedRefs performs a first pass over the page to find the Triaged section divider and record all block-ref UUIDs that are descendants of it.

func createTriagedSectionWithRef

Go
func createTriagedSectionWithRef(page logseq.Page, uuid logseqapi.TaskUUID, triagedText, scheduledText string) error

createTriagedSectionWithRef creates a new Triaged section with a block reference. It inserts the section before the Scheduled section if found, or appends to the end.

func defaultQuery

Go
func defaultQuery(pageTitle string) string

func handleDefaultBlockRef

Go
func handleDefaultBlockRef(node content.Node, blockRef *content.BlockRef, state *pageState, underScheduled bool, futureScheduledBlockRefs *set.Set[string]) bool

handleDefaultBlockRef handles the default case in processBlockRef: moves stale Scheduled tasks back to new, and unpins regular tasks. Returns true if the block should be deleted.

func insertNewTasks

Go
func insertNewTasks(page logseq.Page, state *pageState, newBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string]) bool

insertNewTasks inserts new task refs under the new-tasks divider (creating it if needed). Returns updated save flag.

func insertOverdueTasks

Go
func insertOverdueTasks(page logseq.Page, state *pageState, overdueBlockRefs *set.Set[string])

insertOverdueTasks inserts overdue task refs under the overdue divider (creating it if needed).

Text Only
Sections order: Focus / Overdue / New tasks / all other tasks / Scheduled tasks

Overdue tasks go after the focus section and before new ones so the user can manually decide which overdue tasks deserve focus.

func insertScheduledTasks

Go
func insertScheduledTasks(page logseq.Page, state *pageState, futureScheduledBlockRefs *set.Set[string])

insertScheduledTasks moves future-scheduled tasks to the bottom of the page.

func nextChildHasPin

Go
func nextChildHasPin(node content.Node) bool

func printQuickCaptureURL

Go
func printQuickCaptureURL(graph *logseq.Graph)

func processAllBlocks

Go
func processAllBlocks(page logseq.Page, state *pageState, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string])

processAllBlocks iterates all blocks on the page, records section dividers, and removes or unpins block refs that are obsolete, overdue, or scheduled.

func processBlockRef

Go
func processBlockRef(node content.Node, blockRef *content.BlockRef, block *content.Block, state *pageState, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string])

processBlockRef decides whether to delete, pin, unpin, or keep a block ref.

func queryTasksFromPages

Go
func queryTasksFromPages(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, pageTitles []string, currentTime func() time.Time) (*logseqapi.CategorizedTasks, error)

queryTasksFromPages queries Logseq API for tasks from specified pages. It uses concurrent processing for multiple pages and sequential processing for a single page.

func queryTasksFromPagesConcurrent

Go
func queryTasksFromPagesConcurrent(logseqAPI logseqapi.LogseqAPI, pageTitles []string, tasks *logseqapi.CategorizedTasks, finder logseqext.LogseqFinder, currentTime func() time.Time) (*logseqapi.CategorizedTasks, error)

queryTasksFromPagesConcurrent processes pages concurrently using goroutines.

func queryTasksFromPagesSequential

Go
func queryTasksFromPagesSequential(logseqAPI logseqapi.LogseqAPI, pageTitles []string, tasks *logseqapi.CategorizedTasks, finder logseqext.LogseqFinder, currentTime func() time.Time) (*logseqapi.CategorizedTasks, error)

queryTasksFromPagesSequential processes pages sequentially (original implementation).

func queryTasksFromSinglePage

Go
func queryTasksFromSinglePage(logseqAPI logseqapi.LogseqAPI, pageTitle string, finder logseqext.LogseqFinder) ([]logseqapi.TaskJSON, error)

queryTasksFromSinglePage queries tasks from a single page and returns the JSON tasks.

func recordSectionDivider

Go
func recordSectionDivider(block *content.Block, textValue string, state *pageState)

recordSectionDivider updates state with a block if its text matches a known section header.

func reportCounts

Go
func reportCounts(state *pageState, save bool) bool

reportCounts prints colored summaries and returns updated save flag.

func scanPageBlocks

Go
func scanPageBlocks(page logseq.Page, state *pageState, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string])

scanPageBlocks is a two-pass coordinator: first it normalizes headers, then collects UUIDs already in the Triaged section, then processes all blocks (which uses those UUIDs for deduplication in the regular area).

func sortTriagedSection

Go
func sortTriagedSection(state *pageState, taskLookup map[logseqapi.TaskUUID]logseqapi.TaskJSON)

sortTriagedSection sorts children of the Triaged divider by priority, date, name, and ID.

func taskSortKeyLess

Go
func taskSortKeyLess(left, right taskSortKey) bool

taskSortKeyLess compares two sort keys for ordering in the Triaged section.

type Backlog

Go
type Backlog interface {
    Graph() *logseq.Graph
    ProcessAll(partialNames []string) error
    ProcessOne(pageTitle string, funcQueryRefs func() (*logseqapi.CategorizedTasks, error)) (*Result, error)
}

func NewBacklog

Go
func NewBacklog(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, reader ConfigReader, currentTime func() time.Time) Backlog

type Config

Go
type Config struct {
    FocusPage string
    Backlogs  []SingleBacklogConfig
}

func (*Config) FindBacklogPageTitle

Go
func (c *Config) FindBacklogPageTitle(backlogName string) string

FindBacklogPageTitle looks up the full backlog page path from config by backlog name. Returns empty string if no matching backlog is found.

type ConfigReader

Go
type ConfigReader interface {
    ReadConfig() (*Config, error)
}

func NewPageConfigReader

Go
func NewPageConfigReader(graph *logseq.Graph, configPage string) ConfigReader

NewPageConfigReader creates a new ConfigReader that reads the backlog configuration from a Logseq page.

type Header

Header represents a backlog section divider. Label is the display word(s) without the "tasks" suffix (e.g. "Focus"). String() always returns "Emoji Label tasks".

Go
type Header struct {
    Emoji string
    Label string
}

func (Header) Matches

Go
func (h Header) Matches(blockText string) bool

Matches reports whether blockText contains the label, case-insensitively.

func (Header) NewHeading

Go
func (h Header) NewHeading() *content.Heading

NewHeading returns a level-1 heading node with the canonical header text followed by a [[quick capture]] page link. Use this when creating a new section divider so the user can identify blocks inserted by lqd backlog.

func (Header) String

Go
func (h Header) String() string

String returns the canonical display form: "emoji label tasks".

type Result

Go
type Result struct {
    FocusRefsFromPage *set.Set[string]
    ShowQuickCapture  bool
}

func insertAndRemoveRefs

Go
func insertAndRemoveRefs(graph *logseq.Graph, pageTitle string, newBlockRefs, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string], taskLookup map[logseqapi.TaskUUID]logseqapi.TaskJSON) (*Result, error)

type SingleBacklogConfig

Go
type SingleBacklogConfig struct {
    BacklogPage string
    Icon        string
    InputPages  []string
}

type backlogImpl

Go
type backlogImpl struct {
    graph        *logseq.Graph
    logseqAPI    logseqapi.LogseqAPI
    configReader ConfigReader
    currentTime  func() time.Time
}

func (*backlogImpl) Graph

Go
func (b *backlogImpl) Graph() *logseq.Graph

func (*backlogImpl) ProcessAll

Go
func (b *backlogImpl) ProcessAll(partialNames []string) error

func (*backlogImpl) ProcessOne

Go
func (b *backlogImpl) ProcessOne(pageTitle string, funcQueryRefs func() (*logseqapi.CategorizedTasks, error)) (*Result, error)

type pageConfigReader

Go
type pageConfigReader struct {
    graph      *logseq.Graph
    configPage string
}

func (*pageConfigReader) ReadConfig

Go
func (p *pageConfigReader) ReadConfig() (*Config, error)

ReadConfig reads the backlog configuration from a Logseq page.

type pageState

pageState holds mutable state accumulated while scanning a backlog page.

Go
type pageState struct {
    firstBlock       *content.Block
    dividerNewTasks  *content.Block
    dividerOverdue   *content.Block
    dividerFocus     *content.Block
    dividerScheduled *content.Block
    dividerTriaged   *content.Block
    dividerUnranked  *content.Block

    deletedCount            int
    movedCount              int
    movedScheduledCount     int
    movedFromScheduledCount int
    unpinnedCount           int

    result           *Result
    pinnedBlockRefs  *set.Set[string]
    triagedBlockRefs *set.Set[string] // UUIDs already in the Triaged section
    unscheduledRefs  *set.Set[string] // UUIDs removed from Scheduled because they lost their scheduled date
}

func newPageState

Go
func newPageState() *pageState

type taskSortKey

taskSortKey holds the fields used to sort tasks in the Triaged section.

Go
type taskSortKey struct {
    priority    content.PriorityValue // PriorityNone=0 sorts FIRST (unprioritized at top)
    createdDate time.Time             // oldest first
    firstLine   string                // alphabetical tiebreaker
    id          logseqapi.TaskUUID    // UUID, guaranteed unique final tiebreaker
    block       *content.Block        // reference to the block for reordering
}

func newSortKeys

Go
func newSortKeys(children content.BlockList, taskLookup map[logseqapi.TaskUUID]logseqapi.TaskJSON) ([]taskSortKey, int)

newSortKeys creates sort keys for all children of the Triaged section.

Generated by gomarkdoc