Skip to main content

kanban-gantt

❖ Communityv1.5.0★ 10

Gantt timeline view for the Hermes kanban boards: a full /kanban-gantt desktop page rendering any board as a timeline of real work timestamps (created/started/review/done), with a collapsible tree column (boxed fold controls, tree connectors, per-board fold state, multi-parent links kept visible), an optional List view beside the timeline (task, assignee, status, last activity — the timeline stays the default), task creation (project, workspace, skills, model override, goal mode), re-parenting by drag & drop or from the drawer, live-activity arcs, filters, bulk actions, and a dockable task detail drawer. Zero API keys, zero model tokens; live updates follow the plugin's /events websocket, which a gateway turns off with KANBAN_GANTT_WS=0 (the page then polls every 60 s and shows a Refresh button).

Open in Hermes Desktop
hermes plugins install kanban-gantt

Screenshots

README

From the reviewed commit e735cd2 ↗; it updates when the author re-pins.

Hermes Kanban Gantt

License: GPL-3.0 Tests Zero API keys

A Gantt timeline view plugin for Hermes Desktop — a full-page /kanban-gantt route that renders any Hermes kanban board as a timeline of real work (created / started / done timestamps), not a forecast planner.

Kanban Gantt ── 20 ────────────────────────────────  [◄ zoom ►] [Refresh]
TÂCHES        │ LUN 7 ── MAR 8 ── MER 9 ── JEU 10 ── VEN 11 ── …
☑ Epic X      │        ▓▓▓▓▓▓▓░░░░░░░▓▓▓▓ (running)
☐ Sub-task A  │              ▓▓▓▓▓ (done)

Features

  • Full desktop page (/kanban-gantt) + sidebar entry + ⌘K palette command.
  • Board switcher projected into the desktop page-header band (WORKSPACE_PAGE_HEADER_AREA), styled like the official kanban switcher, with an "All boards" aggregate mode; per-board badges on rows in aggregate view. Requires Hermes ≥ 0.21.5.
  • Real timeline bars — from the kanban tasks' actual timestamps (created / started / review / done), with live-activity arc on running tasks, weekend shading, zoom (7 days at 100%), auto-scroll to "now".
  • List view — a List / Timeline toggle in the header swaps the bars for full-width columns (task, assignee, status, last activity). The choice is persisted; the timeline stays the default.
  • Dependencies — parent → child links drawn as tree connectors in the task column and dependency chains in the timeline.
  • Collapsible tree column — the task column reads like a file explorer: a boxed + / − on every task that has children (a leaf keeps the slot, so titles stay aligned), a vertical down every level that still has a sibling below it plus the ├ / └ elbow for the row itself, and a global toggle in the column header, in front of the master checkbox, that collapses or opens everything. The fold state is persisted per board; a search ignores it — everything opens and matches are flagged — without touching what is stored, so clearing the query restores exactly the branches you had. Selecting a task tints its descendants, which is what makes a child not printed directly beneath its parent visible. On a multi-parent board the task is still printed once, under its first parent: a later parent shows a dashed square, closed by default, which reprints the child without repeating its subtree — so the link is always visible and a task never disappears because its first parent happens to be collapsed.
  • Create tasks — “New task” at the right end of the toolbar (title, description, project, workspace kind + path, assignee, priority, skills, model override, parent, triage, goal mode), and “Create a sub-task” from a task's ⋯ menu (labels come from the locale bundles). The status is derived by the kanban domain — ready, or todo while the chosen parent is unfinished, triage when asked. Parent and project pickers are searchable, assignees come from the board and the Hermes profiles, and an idempotency key per submit means a double click cannot duplicate the task.
  • Re-parent by drag & drop — drag a task's name onto another task to move it under it; refused targets (itself, its own subtree, another board, a done/archived task) light up red before you release. A task that already has parents asks: add a new parent, or replace them with this one — and the parent list can be pruned right there. Full parent/child lists (status icon, click to open that task) live in the detail drawer; the drawer's ⋯ menu also offers a searchable “Move under…” picker for the same operation without the mouse.
  • Task detail drawer — status transitions (action matrix), assignee, comments, runs, attachments; dockable (« / ») beside the gantt instead of overlaying, resizable, position persisted.
  • Bulk operations — multi-select rows (header checkbox, shift-click) → move status, assign, archive, delete.
  • Filters — by assignee, by status, show/hide archived, text search.
  • Readable by design — sticky opaque label column, opaque dropdown menus, resizable label column, all state persisted per plugin.

Creating and moving tasks

Create — “New task” sits at the right end of the toolbar; “Create a sub-task” is in a task's ⋯ menu (parent prefilled). The form covers the same fields as the official kanban dialog: title, description, project, workspace kind (scratch / worktree / dir) with an optional path, assignee, priority, skills, model override, parent, triage and goal mode. The parent and project pickers are searchable; the assignee list merges the board's own assignees with the Hermes profiles, so it is not empty on a fresh board.

The status is never asked for — the kanban domain derives it (ready, todo while the chosen parent is unfinished, triage when requested) and the new task's detail opens straight away, so you see where it landed instead of a promise. One idempotency key is generated per dialog opening: a double click cannot create two tasks.

Move — drag a task's name cell onto another task to move it under it. Nothing is offered that the domain would refuse: the dragged task itself, its own subtree, a task from another board and finished tasks are greyed out, and the drop target turns red before you release. Dropping on a task that already has parents asks whether to add another parent or replace them with this one (existing parents are listed there, each removable). The same operation is reachable without the mouse from the drawer's ⋯ menu (“Move under…”, searchable). Parents and children are listed in the drawer with their status icon, and clicking one selects and opens that task.

Route Purpose
POST /tasks create a task (status derived by the domain)
GET /projects projects the profile declares, for the create form
GET /profiles Hermes profiles, offered as assignees
POST /tasks/{id}/parent link a parent — mode: add or replace
DELETE /tasks/{id}/parent/{parentId} drop one parent link

Every write delegates to hermes_cli.kanban_db and surfaces what the domain decided: a refused move (cycle, running child, cross-board) comes back as a 409 with the domain's own wording, and a re-parented ready child that the domain gated back to todo is reported as such rather than silently flipped.

Install

hermes plugins install e-is/hermes-kanban-gantt

Verify: Mounted plugin API routes: /api/plugins/kanban-gantt/ in ~/.hermes/logs/agent.log, then ⌘K → « Kanban Gantt ».

Layout

plugin.yaml          unified plugin manifest
__init__.py          no-op register() (capability probe only)
dashboard/           backend — FastAPI router → /api/plugins/kanban-gantt/
  manifest.json      name/label/version/api pointer
  plugin_api.py      gantt snapshot, boards, task detail, create/projects/profiles,
                     status/comment/assignee routes, parent links
src/                 AUTHORING SOURCES (TypeScript)
  main.ts            renderer entry — bundled to desktop/plugin.js by esbuild
  state.ts           atoms + the REST door (verb always forwarded)
  core/gantt-core.ts pure timeline logic (no React/SDK)
  ui/                NewTaskDialog, ReparentChooser, TaskRelations, switcher
  sdk.d.ts           ambient types for the SDK subset in use
desktop/             BUILD ARTIFACTS — loaded uncompiled by Hermes Desktop
  plugin.js          bundled renderer (do not edit; run `npm run build`)
  gantt-core.js      bundled pure core (the node tests import it)
install.sh           optional convenience installer (desktop half + backend half)
tests/               node:test suites (pure gantt core, REST door), a stub-render
                     smoke test that mounts the built plugin without a browser
                     (tests/ui/esm-render.mjs), and the pytest backend suite

Build & test:

npm install            # esbuild
npm run build          # src/ → desktop/
npm run check          # syntax-gate the artifacts
npm test               # node suites + stub-render + pytest backend

Architecture

  • Backend owns all data access: reads the shared kanban SQLite store (~/.hermes/kanban/boards/<slug>/kanban.db), serves the Gantt snapshot (rows, bars, domain, dependencies), and routes status transitions / comments / bulk writes through the same domain layer as the core kanban. Mounted at /api/plugins/kanban-gantt/ by the desktop's hermes serve.
  • Renderer owns everything visual: timeline math (pure GANTT_CORE_SRC core, testable in isolation), rows, bars, filters, drawer, and the desktop-page chrome (sidebar entry, palette command, page-header switcher).
  • Zero API keys, zero model tokens — no network I/O beyond the local Hermes gateway and the local SQLite store.

Tests

npm test                                                        # everything below

# pure core + REST door (node:test, no deps)
node --test tests/gantt-core.test.mjs tests/rest-method.test.mjs

# the built plugin, imported as ESM against SDK/react stubs: page registers,
# renders and projects its switcher — no browser, no desktop needed.
# The stub fills in any SDK export the artifact imports, so it cannot drift.
node tests/ui/esm-render.mjs

# backend (isolated venv; HERMES_AGENT_HOME points at a hermes-agent checkout)
tests/run_tests.sh

Visual validation

There is no browser harness: the demo page was retired (it rendered its own simplified DOM, so it proved nothing about the real component tree). Visual work is validated in the desktop itself, where the plugin actually runs — install.sh, then Ctrl+K → "Reload desktop plugins", or drive a debug build over CDP (HERMES_DESKTOP_CDP_PORT=9223 hermes desktop).

Screenshots

npm run shots (Python + CDP) drives the real desktop: it forces the UI to English, switches the active gateway to « This device », collapses the sidebar's Pinned/Sessions sections, opens the plugin on the gantt-demo board and walks a scenario — writing docs/screenshot-en-*.png (what the catalog entry pins) plus a GIF in docs/animations/ assembled with ffmpeg. The desktop has to expose a CDP port first:

pkill -f 'release/linux-unpacked/Hermes'
~/.hermes/hermes-agent/apps/desktop/release/linux-unpacked/Hermes \
    --ozone-platform=wayland --remote-debugging-port=9222

Seed the board it captures with .agents/plans/seed-demo-board.py (parents there are prerequisites — a parent task must be finished before its children run, never a container).

The scenario leaves nothing to the state it finds: it forces the timeline before the first capture (the view mode is persisted, so a previous run could have left the app in list mode), folds a branch for the tree shot, then switches to the list view for the last captures. It also fixes what the shell would otherwise impose — the task column's width (400 px), the light appearance (this machine follows the system, and a switch to dark turned every capture dark) and the sidebar width — and puts the appearance and the sidebar back the way it found them when the run ends.

Catalogue card

The catalogue entry's image: is a 2:1 banner — measured on four entries: 1600x800, 1280x640, 1200x600, 1280x640 — so this one is 1280x640.

It is drawn, not generated: the other entries are terminal-style cards whose typography no image model reproduces reliably (FLUX turns "plugins install" into glyph soup). scripts/card.py composes it in HTML/CSS and rasterises it with the Chromium Hermes ships, so one place holds the wording, the palette and the bars:

npm run card                       # -> docs/catalog-card.png (1280x640)
python3 scripts/card.py --out /tmp/card.png --scale 1

It needs neither the desktop nor a CDP port, and npm run shots calls it at the end of a capture run (skip with --no-card), so the stills and the card stay in sync. The generated docs/catalog-card.html sits next to the PNG on purpose: that file is the source, and editing it (or the constants at the top of card.py) then re-running is the whole workflow.

License

GPL-3.0 — see LICENSE.

← Back to the catalog · catalog built Oct 15, 2026