Repository navigation
Extension Types
Every type an extension sees, from
packages/extension-api/src/index.ts
(@devlog/extension-api) and
view.ts
(@devlog/extension-api/view). All of them are plain data; times are ISO-8601
strings and dates are local YYYY-MM-DD.
import type { DevlogContext, ExtensionCanvas, ExtensionBlock } from '@devlog/extension-api'
import { API_VERSION } from '@devlog/extension-api' // '1.7.0'interface ExtensionCanvas {
id: string
title: string
parentId: string | null
task: boolean
type?: string // (1.6) "<extension id>/<type id>"
archived: boolean
fields: Record<string, string> // this extension's own canvas fields set on this canvas
}| Field | Meaning |
|---|---|
id |
Stable random id; renames and moves keep it. |
parentId |
The enclosing canvas, or null at the top level. |
task |
Whether it is devlog-time's task type (same as type === 'builtin.devlog-time/task'). |
type |
Its node type, if any. |
fields |
Keys as declared in contributes.canvasFields, values set on this canvas only (use devlog.field for the inherited value). |
interface ExtensionBlock {
id: string
createdAt: string
updatedAt?: string
markdown: string
parentId?: string
kind?: string
hidden?: boolean
meta?: Record<string, string>
}| Field | Meaning |
|---|---|
id |
Unique within its day file. |
createdAt / updatedAt
|
When written, and last edited. |
markdown |
The text. Image sources are repo-root-relative (canvases/k3/k3m9x2q7vd/entries/2026/09/assets/x.png). |
parentId |
The block it sits inside, if any. |
kind |
Absent for a note; else commit, task, todo, done, timesheet, or an extension's own kind on a canvas it keeps. |
hidden |
Collapsed in the stream (still there, still searchable). |
meta |
Attributes: ext (the extension that wrote it), canvas (a task block's canvas), done (when a todo was ticked), repo/hash/branch/author (commits), and whatever an extension set. |
interface ExtensionTodo {
canvasId: string
date: string // the day file it lives in
block: ExtensionBlock // kind: 'todo'; meta.done once ticked
trail: Array<{ id: string; title: string }> // blocks it sits inside, outermost first
}interface ExtensionDayBlocks { canvasId: string; date: string; blocks: ExtensionBlock[] }interface ExtensionSearchResult {
blocks: Array<{ canvasId: string; date: string; block: ExtensionBlock }>
}interface AddBlockOptions {
meta?: Record<string, string>
parentId?: string // add inside this block (needs date)
date?: string // the parent's day file; or, on a kept canvas, any day
todo?: boolean // add as a todo
kind?: string // on a kept canvas: its own kind
}interface NewCanvas { title: string; parentId?: string | null; type?: string }interface CanvasPatch {
title?: string
parentId?: string | null
type?: string | null // one of its own types, or null for a plain canvas
archived?: boolean
}interface PickItem { id: string; label: string; hint?: string }interface CommandContext {
source: 'switcher' | 'keybinding' | 'menu' | 'post' | 'view' | 'tray'
canvasId?: string // the canvas on screen, or the one whose menu it was
date?: string // with blockId: the block's day file
blockId?: string // the block whose menu it was, the page on screen, or the block just posted
}date and blockId are given only when the extension may read canvasId.
interface BlockAddedEvent { canvasId: string; date: string; block: ExtensionBlock }interface ActivityNotice {
t: string
type: 'pause' | 'resume' | 'task'
reason?: 'locked' | 'idle' | 'suspended'
canvasId?: string | null
}task notices came from the app's own tracker before 1.6 and are no longer
sent now that time tracking is an extension.
What devlog.activity returns.
interface ActivityRecord {
t: string
type: 'start' | 'stop' | 'heartbeat' | 'lock' | 'unlock' | 'idle' | 'active'
| 'suspend' | 'resume' | 'task' | 'focus' | 'git' | 'exclude'
canvasId?: string | null
entryId?: string
app?: string; title?: string // focus
repo?: string; action?: string; branch?: string; from?: string; detail?: string // git
start?: string; end?: string; id?: string; cancels?: string // exclude
machine?: string
}type |
Meaning |
|---|---|
start / stop
|
The clock started or stopped with the app (from a time provider). |
task |
The active task changed (canvasId, or null: stopped). |
heartbeat |
Still running (the app counts time up to the last one when a log just ends). |
lock / unlock
|
Screen locked / unlocked. |
idle / active
|
No input for the idle threshold / input again. |
suspend / resume
|
Sleep / wake. |
focus |
The window in front changed (app, title). |
git |
Something in a linked repository: action is commit, branch, checkout, push, merge, rebase, pull, stash or reset. |
exclude |
A correction: no task time between start and end; or undoes the exclusion whose id is cancels. |
What a time provider returns.
interface TimeEvent {
t: string
type: 'start' | 'task' | 'stop' | 'heartbeat'
canvasId?: string | null // for task (and start): the active canvas
blockId?: string // the block that started it, if one did
machine: string // ctx.machine where it was recorded
}What a focus provider returns.
interface FocusEvent { t: string; app: string; title: string; machine: string }What a destination receives.
interface DestinationSheet {
week: string // the Monday, YYYY-MM-DD
status: 'draft' | 'final'
entries: DestinationEntry[]
}interface DestinationEntry {
id: string // stable within the week's timesheet
date: string
start: string // ISO, on a local quarter hour
minutes: number // a multiple of 15
note?: string
canvasId: string
task: string // "Client / Project / Task"
client: string // the top-level canvas's title
fields: Record<string, string> // this extension's canvas fields, inherited
}One line of what a destination would do.
interface DestinationLine {
id: string
entryIds: string[] // the timesheet entries it covers
date: string
start?: string
minutes: number
target: string // an issue key, an assignment…
description?: string
action: 'create' | 'update' | 'delete' | 'unchanged' | 'skip'
reason?: string // why, for skip
}interface SendResult {
done: string[] // line ids that went through
failed: Array<{ lineId: string; error: string }>
summary: string // "3 worklogs created, 1 updated"
}interface Destination {
preview(sheet: DestinationSheet): Promise<DestinationLine[]>
send(sheet: DestinationSheet): Promise<SendResult>
}interface DestinationInfo {
extension: string // the extension's devlog.json key
from: string // its display name
id: string
label: string
}What a sender passes to destinations.preview / send; the app turns it
into each destination's DestinationSheet.
interface SheetToSend {
week: string
status: 'draft' | 'final'
entries: Array<{
id: string; date: string; start: string; minutes: number; canvasId: string
note?: string; worked?: number; source?: string
}>
}interface ExtensionFileInfo { path: string; size: number; mtime: string }interface ExtensionFiles {
read(path: string): Promise<Uint8Array | undefined>
readText(path: string): Promise<string | undefined>
write(path: string, data: string | Uint8Array): Promise<void>
append(path: string, text: string): Promise<void>
list(dir?: string): Promise<ExtensionFileInfo[]>
stat(path: string): Promise<ExtensionFileInfo | undefined>
remove(path: string): Promise<void>
}interface ViewContext { canvasId?: string; date?: string; blockId?: string }The postMessage messages between a view and the app; every message carries
devlog: 1. Listed in full in
Views and UI → Message protocol.
DevlogContext is the ctx passed to activate; see
Extension API Reference. ExtensionModule is the
shape of main.js's exports:
interface ExtensionModule {
activate(ctx: DevlogContext): void | Promise<void>
deactivate?(): void | Promise<void>
}Devlog 0.18.0 · extension API 1.7.0 · storage format 4 · Repository · Design notes
Using Devlog
Writing extensions
- Overview
- Quickstart
- Manifest
- API Reference
- Types
- Views and UI
- UI Kit Reference
- Timesheet Destinations
- Activity and Time Data
- Sandbox and Permissions
- Testing
- Built-in Extensions
- API Versions
- Troubleshooting
Devlog internals