412 lines
12 KiB
TypeScript

/** Parsed and normalised ```gtd-calendar config. */
export interface CalendarConfig {
view: "day" | "week" | "month" | "month-grid";
title: string | null;
/** Folder recursion depth: 0 = this folder only, Infinity = all children. */
scopeDepth: number;
/**
* `scope: all` — scan every notebook in the profile, ignoring the root
* folder entirely. When true, `scopeDepth` and `notebook` are irrelevant.
*/
scopeAll: boolean;
/**
* Notebook to root the folder scan at (id, title, or Parent/Child path).
* null = the host note's own folder. `scopeDepth` applies relative to it.
* Ignored when `scopeAll` is true.
*/
notebook: string | null;
/** Notebook roots whose complete descendant trees are removed from scope. */
excludeNotebooks: string[];
notes: InclusionMode;
todos: InclusionMode;
sort: "asc" | "desc";
sortType: "title" | "modified_date";
/** Whether to show the Unscheduled to-dos / notes sub-sections. */
unscheduledTodos: boolean;
unscheduledNotes: boolean;
/** Non-fatal problems found while parsing, surfaced in the UI. */
warnings: string[];
}
export type InclusionMode = "gtd-only" | "all" | "none";
/** A note/todo as fetched from the Joplin data API. */
export interface RawNote {
id: string;
title: string;
parent_id: string;
is_todo: number;
todo_due: number;
todo_completed: number;
updated_time: number;
/**
* Note body. Only fetched when the caller asks for it via
* `getNotesInFolder(folderId, true)`; undefined otherwise. Collectors only
* omit the body when their inclusion mode means the gtd block is never
* scanned, so callers must never read this in a body-less path.
*/
body?: string;
}
/** Properties parsed from a ```gtd block. */
export interface GtdBlock {
date: string | null;
bgColour: string | null;
fgColour: string | null;
icon: string | null;
title: string | null;
text: string | null;
}
export interface GtdBlockResult {
found: boolean;
block: GtdBlock | null;
error: string | null;
}
/** Event payload sent to the webview. */
export interface GtdEvent {
id: string;
title: string;
/** ISO yyyy-mm-dd, or null for unscheduled. */
date: string | null;
isTodo: boolean;
completed: boolean;
bgColour: string | null;
fgColour: string | null;
icon: string | null;
text: string | null;
updatedTime: number;
/** True when the to-do carries the recurrence index tag (see RECURRING_TAG). */
isRecurring: boolean;
}
export interface RawFolder {
id: string;
parent_id: string;
/**
* Notebook title. Needed only to resolve the `notebook:` option by
* name/path (folder scoping uses id/parent_id alone), so it is optional for
* the benefit of scope-only test fixtures; the real adapter always sets it.
*/
title?: string;
}
/** Thin abstraction over the Joplin data API so collectEvents is testable. */
export interface DataAdapter {
getFolders(): Promise<RawFolder[]>;
/**
* Notes in a folder. `includeBody` is a fetch hint: when false, the adapter
* may omit the (potentially large) `body` field. Collectors pass false only
* when their inclusion mode guarantees the gtd block is never scanned.
*/
getNotesInFolder(folderId: string, includeBody: boolean): Promise<RawNote[]>;
/** Lowercased tag titles for a note. Used to detect recurrence. */
getNoteTagTitles(noteId: string): Promise<string[]>;
}
export interface RawTag {
id: string;
title: string;
}
/** Narrow writable Joplin boundary used only by validated workflow moves. */
export interface MutationAdapter {
getNote(noteId: string): Promise<RawNote | null>;
getNoteTags(noteId: string): Promise<RawTag[]>;
getAllTags(): Promise<RawTag[]>;
createTag(title: string): Promise<RawTag>;
attachTag(tagId: string, noteId: string): Promise<void>;
detachTag(tagId: string, noteId: string): Promise<void>;
setTodoCompleted(noteId: string, completedTime: number): Promise<void>;
}
/**
* Tag maintained by the "Repeating To-Dos" plugin family as a query index
* for recurring to-dos. We treat its presence as the recurrence signal.
* Kept as a constant so a future `recurring-tag:` config option (or a
* switch to a different recurrence plugin) is a one-line change.
*/
export const RECURRING_TAG = "recurring";
/**
* Default tag that places a to-do in the kanban "In Progress" column.
* Overridable via the gtd-kanban `in-progress-tag:` option. Constant
* mirrors RECURRING_TAG.
*/
export const IN_PROGRESS_TAG = "in-progress";
/** Default tag that marks an opted-in ordinary note complete. */
export const DONE_TAG = "done";
export type KanbanSortType = "due-date" | "title" | "modified-date";
/** Ordinary-note inclusion for kanban/matrix; notes still require a gtd block. */
export type NoteInclusionMode = "all" | "none";
/** Parsed and normalised ```gtd-kanban config. */
export interface KanbanConfig {
title: string | null;
scopeDepth: number;
/** `scope: all` — scan every notebook; ignores scopeDepth and notebook. */
scopeAll: boolean;
/** Separate exact-owner notebook views; requires children + notebook grouping. */
groupByNotebook: boolean;
/** Notebook to root the scan at (id/title/path); null = host folder. */
notebook: string | null;
/** Notebook roots whose complete descendant trees are removed from scope. */
excludeNotebooks: string[];
notes: NoteInclusionMode;
todos: InclusionMode;
sortType: KanbanSortType;
sort: "asc" | "desc";
inProgressTag: string;
/** Tag that marks an opted-in ordinary note complete. */
doneTag: string;
cardDetail: "hover" | "always" | "none";
/** Cards initially shown, and revealed per "List more" action. */
pageSize: number;
/** Whether this board explicitly opts in to persisted workflow moves. */
editable: boolean;
/** Days; Infinity means "all". */
doneWindow: number;
warnings: string[];
}
/** Fixed semantic destinations accepted from an editable kanban webview. */
export type KanbanDestination = "backlog" | "inProgress" | "done";
/** Constrained intent sent by the webview; the main process derives all writes. */
export interface MoveKanbanCardIntent {
type: "moveKanbanCard";
view: "kanban";
hostNoteId: string;
cardId: string;
destination: KanbanDestination;
rawConfig: string;
viewInstanceId: string;
}
export type MoveCardResult =
| { status: "success"; viewInstanceId: string; changed: boolean }
| { status: "stale"; viewInstanceId: string; message: string }
| { status: "error"; viewInstanceId: string; message: string };
/** A note or to-do rendered as a kanban/matrix card. */
export interface KanbanCard {
id: string;
title: string;
date: string | null;
isTodo: boolean;
completed: boolean;
completedTime: number;
isRecurring: boolean;
bgColour: string | null;
fgColour: string | null;
icon: string | null;
text: string | null;
updatedTime: number;
}
export interface KanbanBoard {
backlog: KanbanCard[];
inProgress: KanbanCard[];
done: KanbanCard[];
}
/** Identity shared by every exact-owner notebook view. */
export interface NotebookViewMetadata {
folderId: string;
notebookPath: string;
cardCount: number;
warnings: string[];
}
export interface NotebookKanbanGroup extends NotebookViewMetadata {
board: KanbanBoard;
}
export type KanbanLayout =
| { kind: "single"; board: KanbanBoard }
| { kind: "notebooks"; groups: NotebookKanbanGroup[] };
/**
* Default tags for the Eisenhower matrix axes. Overridable via the
* gtd-matrix `urgent-tag:` / `important-tag:` options.
*/
export const URGENT_TAG = "urgent";
export const IMPORTANT_TAG = "important";
export type MatrixMode = "skeleton" | "eisenhower";
/** Parsed and normalised ```gtd-matrix config. */
export interface MatrixConfig {
mode: MatrixMode;
title: string | null;
scopeDepth: number;
/** `scope: all` — scan every notebook; ignores scopeDepth and notebook. */
scopeAll: boolean;
/** Separate exact-owner notebook views; requires children + notebook grouping. */
groupByNotebook: boolean;
/** Notebook to root the scan at (id/title/path); null = host folder. */
notebook: string | null;
/** Notebook roots whose complete descendant trees are removed from scope. */
excludeNotebooks: string[];
notes: NoteInclusionMode;
todos: InclusionMode;
sortType: KanbanSortType;
sort: "asc" | "desc";
urgentTag: string;
importantTag: string;
/** Skeleton mode: tag marking active work (shared with the kanban). */
inProgressTag: string;
/** Tag that marks an opted-in ordinary note complete. */
doneTag: string;
/** Skeleton mode: days ahead within which a due date counts as "due soon". */
urgentWindow: number;
cardDetail: "hover" | "always" | "none";
/** Cards initially shown, and revealed per "List more" action. */
pageSize: number;
/** Effective editable state; only valid for distinct-axis Eisenhower mode. */
editable: boolean;
warnings: string[];
}
export type MatrixDestination =
| "topLeft"
| "topRight"
| "bottomLeft"
| "bottomRight";
export interface MoveMatrixCardIntent {
type: "moveMatrixCard";
view: "matrix";
hostNoteId: string;
cardId: string;
destination: MatrixDestination;
rawConfig: string;
viewInstanceId: string;
}
/**
* The four matrix quadrants, positional (row x column):
* topLeft topRight
* bottomLeft bottomRight
*
* Both modes display Do Next / Scheduled / On Deck / Backlog; their axes and
* bucketing semantics remain mode-specific.
*/
export interface MatrixBoard {
topLeft: KanbanCard[];
topRight: KanbanCard[];
bottomLeft: KanbanCard[];
bottomRight: KanbanCard[];
}
export interface NotebookMatrixGroup extends NotebookViewMetadata {
board: MatrixBoard;
}
export type MatrixLayout =
| { kind: "single"; board: MatrixBoard }
| { kind: "notebooks"; groups: NotebookMatrixGroup[] };
/** Display labels for a matrix mode, sent to the webview. */
export interface MatrixLabels {
columns: [string, string];
rows: [string, string];
quadrants: {
topLeft: string;
topRight: string;
bottomLeft: string;
bottomRight: string;
};
}
// ---------------------------------------------------------------------------
// Gantt (SLICE4 data layer)
// ---------------------------------------------------------------------------
export type GanttSortType = "begin-date" | "title" | "modified-date";
/**
* Parsed properties of an item `gtd-gantt` block (one carrying a `project:`
* key). Notes become bars (begin/end), to-dos become milestones (the to-do's
* native due date; begin/end on a to-do are ignored with a warning).
*/
export interface GanttItemBlock {
/** Non-null marks this as an ITEM block; null/absent means a CHART block. */
project: string | null;
beginDate: string | null;
endDate: string | null;
bgColour: string | null;
fgColour: string | null;
title: string | null;
text: string | null;
}
export interface GanttBlockResult {
found: boolean;
block: GanttItemBlock | null;
error: string | null;
}
/** Parsed and normalised CHART `gtd-gantt` config (no `project:` key). */
export interface GanttConfig {
title: string | null;
scopeDepth: number;
scopeAll: boolean;
notebook: string | null;
/** Limit the chart to a single project (case-insensitive); null = all. */
filterProject: string | null;
sortType: GanttSortType;
sort: "asc" | "desc";
cardDetail: "hover" | "always" | "none";
warnings: string[];
}
/** A note rendered as a gantt bar (a dated span). */
export interface GanttBar {
id: string;
title: string;
/** yyyy-mm-dd, inclusive. */
beginDate: string;
endDate: string;
bgColour: string | null;
fgColour: string | null;
text: string | null;
updatedTime: number;
}
/** A to-do rendered as a gantt milestone (a single dated point). */
export interface GanttMilestone {
id: string;
title: string;
/** yyyy-mm-dd (the to-do's due date). */
date: string;
completed: boolean;
isRecurring: boolean;
bgColour: string | null;
fgColour: string | null;
text: string | null;
updatedTime: number;
}
/** One swimlane: all bars + milestones sharing a `project:`. */
export interface GanttProject {
name: string;
bars: GanttBar[];
milestones: GanttMilestone[];
}
/** Full gantt payload: projects plus the overall date envelope. */
export interface GanttChart {
projects: GanttProject[];
/** Earliest/latest dates across all items; null when the chart is empty. */
rangeStart: string | null;
rangeEnd: string | null;
warnings: string[];
scannedFolders: number;
scannedNotes: number;
itemCount: number;
}