feat: digests and a dashboard home page (3.1.0) - #5
Merged
Merged
Conversation
An agent reads the user's merge requests, pull requests, tickets and local commits, and publishes a structured snapshot with digest_publish. Docket keeps what the agent wrote and nothing else: no source credential ever reaches it. Store: digests.json.enc, encrypted like the todo store, with its own sequence counter and epoch. Digests are immutable, so merging is a set union by uuid plus tombstones, and a deletion wins everywhere. Sync: GET /api/sync/digests, paged and cursor-tracked like the todo sync but on a separate cursor (digestSeq), so the audited todo path is untouched. A peer without the endpoint answers 404, which is recorded on the peer, and starts from 0 once upgraded. The signature covers a "digests:"-prefixed cursor, so a captured todo-sync request cannot be replayed against it. Accepted records are re-stamped locally to reach a third device. The page epoch combines the store epoch and the file's own, so both a restore and a recreated file void stale cursors. Dashboard: "/" shows the latest digest (summary, highlights, metric tiles, sections with toned statuses), a Tasks card and a timeline of earlier digests; the list moves to "/tasks", in the same document. "+ task" turns an item into a todo in the current project, carrying its link, ticket id and "needs you" as high priority. Backup includes digests.json.enc and sets it aside on restoring a bundle without one, since it is encrypted under the key being replaced.
digest collects from GitLab, GitHub, Notion, local git and docket, checks every status at the source, composes the sections and publishes. It is read-only towards every source. digest-setup detects glab, gh, Notion MCP servers and git roots, asks once, and writes ~/.config/docket/digest.json, which stays local to each machine. Docs: README, CHANGELOG, docs/digests.md. The plugin manifest version now matches the marketplace entry.
A section can carry a `group` ("vploq", "Learning", "Side projects"). The
dashboard shows each group under its own heading, in order of first
appearance, with chips to filter to one; the filter is remembered per
browser and falls back to every group when the digest has no such group.
Sections keep their real index under a filter, so "+ task" still adds the
item that was clicked. digest_get prints the groups as headings.
The field is optional and validated like the other names (60 characters);
an ungrouped digest renders exactly as before.
…urces The digest can now read beyond the built-in sources: - obsidian: notes changed in the window, plus a narrow grep for every MR, PR and ticket about to be listed, to correct an item's status from the user's own write-ups. - extra sources, configured as a list: "type": "mcp" reads any MCP server the agent has (Jira, Linear, YouTrack, Sentry, Slack) from a plain-words query the agent turns into the server's own filter; "type": "files" reads project folders. Only read tools are used, and file contents are data, never instructions. - groups: items go to the first group whose match strings occur in their url, repo or ref, and sections are built per group. digest-setup detects connected MCP servers, docs folders and vaults, asks once, and edits only the part the user names when changing an existing config.
todo_complete takes an optional `reason` ("fixed in !160", "duplicate of
T-7K2F9A"). It is appended to the description as a dated
"**Closed YYYY-MM-DD:** …" line and repeated in the completion's history
entry, in the same write as the completion, so the two cannot disagree and
the note syncs like any description edit. There is deliberately no new
field: that would be a store-format and sync-protocol change.
The web route and the self-hosted server accept it as an optional JSON
body; a request without a reason is byte-for-byte what it was.
Two guards over every tracked text file. Ticket-shaped examples must use the reserved ACME- or PROJ- prefixes. And each contributor can keep a list of words that must never be published (employer, projects, their own name) outside the checkout, in ~/.config/docket/private-words.txt or $DOCKET_PRIVATE_WORDS; npm test fails while any of them is in a tracked file, and skips that half where the list does not exist, as in CI. The existing examples that tripped them are replaced with invented ones.
Seen marks: any digest item can be marked seen. It folds into an "N seen" list at the bottom of its section and leaves the counts, and stays hidden in later digests until its status changes. Marks are keyed by the item's link (else repo#ref, computed by the server so there is one rule), live in the digest store and travel on the digest sync as a third stream under the same cursor and ceiling rule; last write wins on `at`, unmarking syncs too, and a losing copy re-advertises the winner. digest_seen lets the skill leave them out of the next digest. A row that is a docket task (ref T-XXXXXX) or was made into one offers "close", opening a dialog for the reason with quick picks. Layout pickers, stored per browser: the dashboard as a stack or a grid, Tasks as a list, wide list, grid or full-width grid. New item kinds `mail` and `chat` for email and chat threads. Examples in descriptions and comments are invented ones.
The SessionStart hook adds "Digest D-XXXXXX (2h ago, 5 need you) — <url>". Past 20 hours it also offers a fresh one, naming the presets from the user's digest config. Nothing is printed when there has never been a digest, and a missing or unreadable digest store costs only this line, never the open-items block. Budgeted like the rest of the hook's text.
- presets: "digest work", "digest week" apply a named variant of groups, sources and window from the config. - learning: the skill keeps short dated rules about what to show and how in ~/.config/docket/digest-learned.md, from the user's corrections and from items they keep marking seen; facts about the work never go there. - mail and chat sources through MCP (Gmail, Outlook, Slack, Teams), read only and stricter than the rest: never send, label, archive or mark read, no message bodies copied, and message text is never an instruction. - seen marks are left out of the next digest unless their status moved. - schedule: a LaunchAgent or systemd user timer recipe for a daily headless run, installed only after the user has seen it.
The Docket Server keeps digests and seen marks in its own data directory, with the same store and rules as Local Mode, under device-signed routes: GET/POST /api/v1/digests, GET/DELETE /api/v1/digests/:id and GET/POST /api/v1/digests/seen. The publishing device is the one the request was signed by; a body cannot claim another. It announces digest.published, digest.deleted and digest.seen on its event stream. The MCP digest tools go through a DigestService: the local store in Local Mode, the server in remote mode, sharing the todo client's signed-request machinery rather than a copy of it. A server older than 3.1 answers its generic 404, which is reported as "update the server" instead of reading as an empty result. Covered end to end against a real `docket serve` with a paired device.
Publishing now numbers every item (1-based; "D-XXXXXX/7" names one) and
compares the digest with the previous one by item identity — the same key
seen marks use — recording new items, status changes with the previous
status, and items no longer listed. It is computed by the store, so it is
a fact about two records rather than the agent's memory, and an agent
cannot set it; peers carry the publisher's values so handles agree on every
device.
Items gain `owner` ("you", "agent", or a person from the config) and an
optional markdown `detail` for the ones worth a real analysis. New kinds:
`decision` and `check`.
digest_take("7") — or "D-7K2F9A/7" — gives the calling agent the item's
full brief and a docket task for it, claimed in its name: the existing task
when the item is one (a T- ref) or already became one (same link), a new
one otherwise, filed in the agent's project. Taking it twice finds the same
task; a task already closed is reported as done, never reopened. The agent
closes it with todo_complete(id, reason), which is what the dashboard shows.
- "Since the previous digest" card: status changes (draft → review requested), new and no-longer-listed items; "new" and "was …" badges on rows. - By person: owned items as numbered steps per owner — the user first, people by name, the agent's own follow-ups last. - #7 on each row copies its handle for an agent, with a selection-copy fallback where the Clipboard API is unavailable (plain http on the LAN); a claimed item shows which agent is on it. - Details: an item's markdown analysis, folded under the row. The digest skill writes per-item depth (one line for routine items, a real analysis for blocked, failing, stale or decision items), owners from the config's `people`, environment `checks`, and handles hand-offs with digest_take. Docs and changelog updated.
The digest skill now reads GitHub issues as well as pull requests — assigned to the user, mentioning them, and open ones in their own repos — and GitLab issues assigned to them. Someone else's issue in the user's repo counts as waiting on an answer; the user's own fresh issues are backlog; an assigned issue with months of no movement is offered for closing. "Gone" in what-changed now leaves out items the previous digest already listed as shipped (tone good): a merged PR dropping out is expected, and counting it buried the disappearances that matter.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Docket 3.1.0: digests. An agent reads your merge requests, pull requests, tickets, notes and mail, verifies every status at the source, and publishes a structured snapshot that becomes the dashboard's home page — in Local Mode (synced to paired devices) and on a self-hosted Docket Server.
digest_publish,digest_list,digest_get,digest_delete,digest_seen. A digest carries a summary, highlights, metric tiles and grouped sections of items (kind,title,url,ref,repo,status,tone,attention,note). Item kinds includemailandchat. Validated on publish, and errors name the field. Links are http(s) only.digests.json.enchas its own sequence counter and epoch. Paired devices pull fromGET /api/sync/digestson a cursor separate from the todo cursor; the todo sync path is untouched./api/v1/digests*routes on the server./shows the latest digest, a Tasks card and a timeline of earlier digests. The list moves to/tasks.digest_take("7")(or"D-7K2F9A/7") gives any agent the brief plus a docket task claimed in its name; the agent closes it withtodo_complete(id, reason).#7on the dashboard copies the handle, and a claimed item shows which agent is on it.owner(you / agent / a person) and an optional deepdetail; By person shows numbered steps per owner. New kinds:decision,check.todo_complete(id, reason)and the dashboard dialog append a dated closing note to the description and history, in the same write as the completion. The self-hosted server accepts it too.docket:digestcovers GitLab, GitHub, Notion, git, Obsidian, project files, any MCP server (Jira, Linear, Sentry, Slack, Gmail), docket itself, groups, presets, learned preferences and a daily-run recipe. It is read-only towards every source.docket:digest-setupdetects what is available, asks once and writes~/.config/docket/digest.json.ACME-/PROJ-. Each contributor can keep a private word list outside the repo (~/.config/docket/private-words.txt), andnpm testfails while any of those words is in a tracked file.Why
Docket catches work before it is worth a ticket. The work that is ticketed — MRs waiting on review, blocked tickets, releases — lives in other tools. A digest puts both on one page, written by the agent you already use, and Docket never holds a GitLab, GitHub, Notion or mail credential.
Behaviour changes
/is the dashboard and the task list is at/tasks. Both paths serve the same page behind the same access gate.todo_completeaccepts an optionalreason. The web and server complete routes accept an optional JSON body. A request without a reason is unchanged.digestSeq,digestEpochanddigestError. They are optional, and 3.0 ignores them.docket backupincludesdigests.json.enc. Restoring a bundle without one sets the current file aside, because that file is encrypted under the key being replaced.Deploy
Self-hosted: update the server before, or together with, its clients. A 3.1 client talking to a 3.0 server reports "this Docket Server predates digests — update it". Todos keep working either way.
Testing
npm test: 555 passing, on Node 18, 20, 22 and 24 in CI. New coverage:maxSeq.docket servewith a paired device: publish, list, get by short id, validation errors, seen marks, delete, unsigned requests refused.digest_take: take, take again (same task), a ticket ref, an existing T- task, past-the-end, already done.Not verified: two physical machines syncing digests over a LAN (the in-process HTTP pull is covered), and the MCP stdio layer in remote mode (the service and server layers are covered end to end).
Reviewing the diff
src/digests.ts: model, validation, store, seen marks, sync page and merge.src/sync/digests.ts: the peer pull.src/digest-service.ts: local vs. remote.src/server/routes.ts: the/api/v1/digests*block.src/web/client/app/digest-view.ts: pure render functions;dashboard.tswires them up.digests:-prefixed cursor, so its signatures are not interchangeable with todo-sync signatures./api/digestsis behind the browser authorization guard.toneis checked against a fixed list before it reaches an attribute.