Index

mmo-kanban

1 symbol · generated 5 Oct 2026

element

mmo-kanban

mmo-kanban.jhs:1

A project board in the manner of GitHub Projects: tickets seen as a board, as a tree-shaped list or as a timeline (a Gantt chart drawing the constraints between tickets).

<mmo-kanban mode="board" group="status" zoom="week" readonly lenient busy></mmo-kanban>
Extends
CustomShadowElement
The board's columns are only a way of looking at the tickets: they are made from the tickets' `status`, their `labels` or their `category`, whichever the board is grouped by, and dropping a card in a column gives it that column's status, label or category. The statuses, the labels and the categories are the board's own, edited in its settings (or in the data). A ticket can hold sub-tickets (`parent`), and a constraint: a tree of conditions, combined with AND, OR and NOT, that the board enforces when a ticket changes. Its data is a plain object, read and written through the `data` property or given inline as a `<script type="application/json">` child: { "statuses": [{"id": "todo", "label": "Todo", "type": "backlog", "limit": null}, ...], "labels": [{"id": "release", "label": "release"}, ...], "categories": [{"id": "web", "label": "Website", "color": "purple", "icon": "globe"}, ...], "people": [{"id": "marc", "label": "Marc Mouret", "avatar": "https://..."}, ...], "tickets": [{"id": "3", "title": "Ship", "status": "todo", "labels": ["release"], "category": "web", "parent": "1", "start": "2026-10-05", "end": "2026-10-09", "assignee": "marc", "description": "", "constraint": {"type": "depends", "ticket": "2"}}, ...] } A status, a label or a category may have a `color` (gray, blue, teal, green, yellow, orange, red, pink, purple, or a `#rrggbb`) and an `icon` (an `<mmo-icon>` name), set in the board's settings; without them a status takes its type's, a label or a category one of the colours by its name. `people` are who a ticket can be assigned to (anyone else typed in is welcome too). The statuses default to backlog, todo, in progress, review / testing and done. A status's `type` is what a ticket with it is: `backlog` (not started), `active` (started) or `done`; giving a ticket a status stamps `startedAt` / `doneAt`, which is what the constraints are checked against. A label a ticket uses and the board does not know is added to the board's labels. The conditions of a constraint, for the ticket B that holds it: - `{"type": "depends", "ticket": "A"}` B depends on A: B cannot start before A is done. - `{"type": "after", "ticket": "A"}` B must be done after A: B cannot be done before A is. - `{"type": "before", "ticket": "A"}` B must be done before A: A cannot be done before B is. - `{"type": "partOf", "ticket": "A"}` B is part of A: A cannot be done before B, and B lies within A on the timeline. A sub-ticket is part of its parent without saying it. - `{"type": "start", "op": "before|at|after", "date": "2026-10-05"}` B must start before / on / after that date, and `{"type": "end", ...}` the same for when B is done. - `{"type": "and", "items": [...]}`, `{"type": "or", "items": [...]}`, `{"type": "not", "item": {...}}`. Each condition is true, false or still open (A is not done yet), and AND / OR / NOT combine the three as a three-valued logic does. A move that makes a constraint false — any ticket's, not only the one moved — is refused and the reason shown, unless the board is `lenient`, which only warns. On the timeline, the same conditions are checked against the planned dates instead, and drawn: an arrow per condition on a ticket, a flag per condition on a date, red where the plan breaks it. A ticket's discussion is shown in its editor once the page gives the board a `discussion`: an object whose functions reach wherever the comments are, each returning a promise (`load` is required, the others let the viewer write; a `readonly` board only reads): board.discussion = { load: (ticket) => ..., // its comments, or null when the ticket has no discussion add: (ticket, body, parent) => ..., // the new comment; `parent` the id of the one it answers, or null update: (ticket, comment, body) => ..., // the comment as it is now remove: (ticket, comment) => ... }; On the server, `MMO\client\controllers\KanbanController` serves a board (`kanban/{board}.json`, and its tickets' discussions) from any repository of `KanbanBoard`, `KanbanTicket` and `KanbanComment` entities, and commits the board's changes to it: a JSON file, a database, GitHub (mmo.git's `GitHubRepository`). A comment is `{id, parent, author: {login, name, avatar, url}, role, body, createdAt, updatedAt, url, mine}`: `body` is markdown (written in `<mmo-markdown-editor>` when the page has it, its html never live), `parent` the comment it answers, `mine` whether the viewer may change it. They are shown in threads, a reply under what it answers, or by date, oldest or newest first: the order the viewer last picked. A ticket's `comments` is how many it has, shown on its card. A description's fenced blocks whose language is an AI agent's name (`AGENTS`) are prompts for that agent: ```` ```claude ````, ```` ```chatgpt ````, ```` ```gemini ````..., ```` ```ai ```` for any. Its card shows which agents it has prompts for, and the search finds them (`ai:` the tickets with a prompt, `ai:claude` with a claude one). On the server, `MMO\client\classes\KanbanPrompts` reads them the same way: the kanban controllers' `prompts.json` hands an agent the tickets meant for it.

Index

modewhat is shown: board (default), list or timeline
groupwhat the board's columns are made from: status (default), label or category
zoomthe timeline's scale: day, week (default) or month
readonlynothing can be moved or edited
lenienta move that breaks a constraint is made anyway, with a warning, instead of refused
busythe board waits on its data, or on a save: a spinner says so, with the attribute's text if it has one (busy="Saving…").
MODES static
SCALES staticpixels per day, by zoom
ROW staticthe timeline's geometry: header, row and bar heights
HEAD static
BAR static
GROUPS static
STATUSES static
TYPES static
AGENTS staticthe languages of a description's fenced block that make it an AI prompt: an agent's name, ai for any
COLORS staticthe colours a status, a label or a category can take: the theme's, so they follow it into the dark
ICONS staticthe icons the settings offer
TYPE_LOOKS statica status's look by its type, until it has its own
EDITOR_WIDTH staticthe width a ticket's panel asks the application for, in pixels: the user's own wins
EDITOR_COMPACT staticbelow this width an editor is one column, its properties above the rest
CONDITIONS staticthe conditions, by type: what the editor calls them and what kind of value they take
OPS static
ORDERS staticthe orders a discussion is shown in
THREAD_DEPTH statica reply deeper than this is shown at this depth, saying whom it answers
ViewDefault static readonly accessor
mode accessorWhat is shown: board, list or timeline.
group accessorWhat the board's columns are made from: status, label or category.
zoom accessorThe timeline's scale: day, week or month.
readonly accessorWhether nothing can be moved or edited.
lenient accessorWhether a move breaking a constraint is made anyway (with a warning).
busy accessorWhether the board waits on its data, or on a save: true, or what it waits on ('Saving to GitHub…').
data accessorThe board's data: a copy of its statuses, labels, categories and tickets.
discussion accessorWhere the tickets' discussions are: {load, add, update, remove} (the class's documentation), or null for none.
prompts(text) staticThe AI prompts of a ticket's description: as MMO.core.markdown reads a fence, and KanbanPrompts::Extract on the server -- a block nested in another fence is text.
constructor()
init()
isSaving(id)Whether a ticket waits on a save: it cannot be changed until it is over (busy).
refuseSaving(id)refuse a change of a ticket being saved, saying so: true when refused
drawLocks()show which tickets are being saved: their cards, rows and bars, and their editors (busy)
drawBusy()show, or hide, what says the board is busy (busy)
load(data)Replace the board's data.
configure(lists)Set the board's statuses, labels and categories, from lists of objects or of names.
lookOf(item)
learnLabels(tickets)the labels used by tickets and not known yet join the board's
isOn(value)
normalizeTicket(t)Make a ticket the board can use, whatever it was given.
normalizeConstraint(node)Make a condition tree the board can use: an array is an AND, an unknown type is dropped.
index()rebuild the lookups after a change of this.tickets
childrenOf(tickets)
nextId()
isAncestor(ancestor, id)Whether a ticket is an ancestor of another (or the same).
statusType(id)
defaultStatus()the status of a new ticket: the first that is not the backlog, when there is one before work starts
toDay(value)────────────────────────────────────────────── Dates: days are 'YYYY-MM-DD' strings, events are local 'YYYY-MM-DDTHH:MM:SS' stamps ──────────────────────────────────────────────
localDay(date)
now()
dayNumber(day)
dayString(n)
formatDay(day)
compareTime(a, b)Compare two times at the precision of the less precise: a day against a stamp compares the days.
world(tickets, now)The world a constraint is checked in: tickets, their lookups and the time.
isDone(t)
isStarted(t)
evaluate(node, b, world, mode)Evaluate a condition tree for a ticket.
stateTicket(type, a, b)a condition on another ticket, against what happened
planTicket(type, a, b)a condition on another ticket, against the planned dates
stateDate(node, b, world)a condition on a date, against what happened: an event not yet happened may still keep it
planDate(node, b)a condition on a date, against the planned dates
fullConstraint(t, world)A ticket's whole constraint: its own, and being part of its parent.
violations(tickets, now)The tickets whose constraint is broken.
attempt(mutate)Try a change on a copy of the tickets: what it would break.
commit(mutate, moved)Make a change, unless it breaks a constraint (and the board is not lenient).
whyBlocked(t)Why a ticket cannot go to its next step (start, or be done), if it cannot.
describe(node, world, nested)Describe a condition tree in words.
failing(node, b, world, mode)Describe what makes a condition tree false: the items of an AND that are, the rest whole.
shorten(text, length)
leaves(node, negated, alternative, found)The conditions of a tree that name another ticket or a date, with how they are reached.
prune(node, id)Remove the conditions naming a ticket.
setStatus(t, status, now)Give a ticket a status, stamping when it started and was done.
moveTicket(id, group, to, beforeId, from)Drop a ticket in a column of the board: give it that column's status, label or category, and put it before another ticket.
addTicket(ticket)Add a ticket.
updateTicket(id, changes)Change a ticket's fields (a new status stamps it as a move does).
removeTicket(id)Remove a ticket: its sub-tickets go to its parent, and the conditions naming it go.
schedule()refresh once, after whatever else changes now
refresh()
matchesFilter(t)whether a ticket matches the filter
look(kind, item)How a status, a label or a category looks: its own colour and icon, or its kind's.
labelItem(id)
badge(kind, item, text)A status, a label or a category as a pill, in its colour, with its icon.
person(person, css)Someone, as a picture (or initials) and a name.
assignable()
icon(name, css, title)
stateIcon(t)the state of a ticket, as an icon: broken, blocked, done
columnsFor(group)The board's columns for a grouping: one per status, label or category — and, for labels and categories, one first for the tickets that have none.
renderBoard(world)
mark(col)what stands for a column before its name: its icon, in its colour, or a dot
scrollToColumn(id)bring a column of the board into sight
markVisibleColumn()the strip of columns says which one is in sight
card(t, world, col)
onCardKey(event, t, col)Enter opens a card; Alt+arrows move it across columns and within its column
placePlaceholder(cards, clientY)the gap a dragged card shows where it would land, in a column at a height of the screen
placeholderNext()the ticket the dragged card goes before: the card after the gap, null for the end
clearDropMarks()
wireDrop(cards, group, col)a column's cards take the dragged one, at the place the pointer shows
wireTouchDrag(card, t, col)Drag a card with a finger: a long press picks it up (a quick swipe still scrolls), it follows the finger, the board scrolls when it nears an edge, and it drops in the column under it — or on a colu…
startTouchDrag(card, t, col, start)
moveTouchDrag(drag)
endTouchDrag(drag, cancelled)
scrollParent()the box the page scrolls in, through the shadow roots and slots this element sits in
quickAddFooter(col)
rows(world)The tickets in tree order, as rows: the filtered ones and their ancestors, below an unfolded parent.
toggle(id)
toggleButton(row)
renderList(world)
renderTimeline(world)
route(from, to, kind)The path of an arrow between two bars, in right angles.
wireBars(svg, bars, px)a bar is moved by its body and resized by its ends, a day at a time
edit(id, defaults)Open a ticket, or a new one, in the side panel of the application (<mmo-app>): a tab per ticket, the same ticket twice shows its tab again.
usesPanels()whether tickets open in the application's side panel (its tabbed one) rather than in a dialog
panelKey(id)a panel's key: the same ticket of the same board is one panel
editorTitle(editor)
refreshEditors()Draw the open editors again after a change of the board: the ones with changes not saved are left as they are, the ones whose ticket is gone close.
lockEditor(editor)An editor whose ticket is being saved waits for it: its fields cannot be reached, and it says why (busy).
closeEditor(editor)close an editor: its panel, or the dialog
renderEditor(editor)Draw an editor: its tabs (the ticket, its discussion), and the ticket's form -- its text on the left, its properties on the right, picked from dropdowns.
drawTabs(editor)The tabs of an editor -- the ticket, its discussion -- drawn again when the count of comments changes; only an existing ticket with a discussion has them.
isCompact(editor)whether an editor is too narrow for two columns
watchEditorWidth(editor)an editor goes to one column, and back, as its panel or its dialog is resized
dropdown(box, button, pop, onOpen)A popover that opens under its button, and closes on Escape or on a click anywhere else.
picker(spec)A dropdown to pick a value, or several: a button showing what is picked, and a list to pick from, filtered by what is typed in it.
lookPicker(kind, item)The colour and the icon of a status, a label or a category, in the board's settings: a button showing them, and a popover to pick them.
descriptionEditor(t, ro, bind)The description of a ticket: markdown, in <mmo-markdown-editor> (a plain textarea when the page does not have it).
addTriggers(editor, id, people)# suggests the board's tickets, @ the people: the ones tickets are assigned to, and more.
discussionSection(editor)Start the discussion of an editor's ticket: its state lives in editor.talk, its section is drawn by
canComment()whether the viewer may write in the discussions
loadDiscussion(editor) async(Re)load the comments of an editor's ticket.
normalizeComment(c)
countComments(editor)the card says how many comments its ticket has
drawDiscussion(editor)Draw an editor's discussion: its header (the count, the order, reload), its comments, and the editor of a new one.
commentList(editor)The comments, in the viewer's order: by date, or in threads -- a reply under what it answers, a reply to a comment that is gone starting a thread of its own.
commentNode(editor, c, byId, sayParent)A comment: who wrote it and when, what it says, and what the viewer can do with it.
commentEditor(editor, kind, target)The editor of a comment: a new one, a reply, or a change of one's own.
focusComposer(editor, key)put the caret in a comment's editor, once it is drawn
sendComment(editor, kind, target) asyncSend what a comment's editor holds: a new comment, a reply, or a change.
removeComment(editor, c) asyncDelete one's own comment, once asked: its replies stay, answering a deleted comment.
showComment(editor, id)scroll to a comment of the discussion, and make it flash
setDiscussionOrder(order)
storedOrder()
ago(date)
fullDate(date)
initials(name)
conditionEditor(t, node, set, redraw, preview, root)The editor of one condition, and of the ones it holds.
newCondition(t)
convertCondition(t, node, type)a condition turned into another type, keeping what both share
save(t, isNew)Save a ticket being edited.
configureBoard(lists)Change the board's statuses, labels and categories.
editSettings()Open the board's settings: its statuses, labels and categories.
confirmRemove(id, editor) async
notify(title, lines, tone)Say something for a while, under the toolbar.
(default)a <script type="application/json"> holding the board's data

Attributes6

mode

mmo-kanban.jhs

what is shown: board (default), list or timeline

mode="board"

group

mmo-kanban.jhs

what the board's columns are made from: status (default), label or category

group="status"

zoom

mmo-kanban.jhs

the timeline's scale: day, week (default) or month

zoom="week"

lenient

mmo-kanban.jhs

a move that breaks a constraint is made anyway, with a warning, instead of refused

lenient

busy

mmo-kanban.jhs

the board waits on its data, or on a save: a spinner says so, with the attribute's text if it has one (busy="Saving…"). Without a ticket yet, it stands in for the board. The tickets changed since it was last busy are the ones being saved: until it is not, they cannot be moved, edited or deleted, and say so

busy

Constants18

SCALES

staticmmo-kanban.jhs:153

pixels per day, by zoom

static SCALES = {day: 34, week: 15, month: 4};

ROW

staticmmo-kanban.jhs:156

the timeline's geometry: header, row and bar heights

static ROW = 36;

STATUSES

staticmmo-kanban.jhs:162
static STATUSES = [ {id: 'backlog', label: 'Backlog', type: 'backlog', limit: null}, {id: 'todo', label: 'Todo', type: 'backlog', limit: null}, {id: 'progress', label: 'In progress', type: 'active', limit: null}, {id: 'review', label: 'Review / testing', type: 'active', limit: null}, {id: 'done', label: 'Done', type: 'done', limit: null} ];

TYPES

staticmmo-kanban.jhs:170
static TYPES = {backlog: 'Not started', active: 'Started', done: 'Done'};

AGENTS

staticmmo-kanban.jhs:173

the languages of a description's fenced block that make it an AI prompt: an agent's name, ai for any

static AGENTS = ['claude', 'chatgpt', 'gpt', 'openai', 'codex', 'gemini', 'mistral', 'copilot', 'cursor', 'llama', 'deepseek', 'grok', 'perplexity', 'ai'];

COLORS

staticmmo-kanban.jhs:201

the colours a status, a label or a category can take: the theme's, so they follow it into the dark

static COLORS = { gray: 'var(--mmo-color-text-3)', blue: 'var(--mmo-color-viz-1)', teal: 'var(--mmo-color-viz-2)', green: 'var(--mmo-color-ok)', yellow: 'var(--mmo-color-warn)', orange: 'var(--mmo-color-viz-4)', red: 'var(--mmo-color-bad)', pink: '#D6409F', purple: 'var(--mmo-color-viz-3)' };

ICONS

staticmmo-kanban.jhs:214

the icons the settings offer

static ICONS = [ 'circle', 'circle-dot', 'circle-half-stroke', 'circle-play', 'circle-check', 'circle-xmark', 'circle-stop', 'circle-exclamation', 'status-draft', 'status-pending', 'status-active', 'status-validated', 'status-final', 'status-archive', 'hourglass-half', 'eye', 'magnifying-glass', 'flag-checkered', 'bug', 'star', 'lightbulb', 'screwdriver-wrench', 'code', 'palette', 'book', 'shield-halved', 'lock', 'tag', 'folder', 'bell', 'calendar-days', 'clock', 'users', 'user', 'globe', 'database', 'desktop', 'chart-line', 'flask', 'comments', 'question', 'triangle-exclamation', 'ban', 'ticket', 'bullhorn', 'gear', 'road', 'shapes', 'building', 'handshake' ];

TYPE_LOOKS

staticmmo-kanban.jhs:224

a status's look by its type, until it has its own

static TYPE_LOOKS = { backlog: {color: 'gray', icon: 'circle'}, active: {color: 'blue', icon: 'circle-half-stroke'}, done: {color: 'green', icon: 'circle-check'} };

EDITOR_WIDTH

staticmmo-kanban.jhs:231

the width a ticket's panel asks the application for, in pixels: the user's own wins

static EDITOR_WIDTH = 760;

EDITOR_COMPACT

staticmmo-kanban.jhs:234

below this width an editor is one column, its properties above the rest

static EDITOR_COMPACT = 600;

CONDITIONS

staticmmo-kanban.jhs:237

the conditions, by type: what the editor calls them and what kind of value they take

static CONDITIONS = { depends: {label: 'Depends on', kind: 'ticket'}, after: {label: 'Is done after', kind: 'ticket'}, before: {label: 'Is done before', kind: 'ticket'}, partOf: {label: 'Is part of', kind: 'ticket'}, start: {label: 'Starts', kind: 'date'}, end: {label: 'Ends', kind: 'date'}, and: {label: 'All of (AND)', kind: 'group'}, or: {label: 'Any of (OR)', kind: 'group'}, not: {label: 'Not', kind: 'not'} };

ORDERS

staticmmo-kanban.jhs:2842

the orders a discussion is shown in

static ORDERS = {thread: 'Threads', oldest: 'Oldest', newest: 'Newest'};

THREAD_DEPTH

staticmmo-kanban.jhs:2845

a reply deeper than this is shown at this depth, saying whom it answers

static THREAD_DEPTH = 4;

Properties9

mode

accessormmo-kanban.jhs:326

What is shown: board, list or timeline.

get mode() set mode(value)
Type
string

group

accessormmo-kanban.jhs:341

What the board's columns are made from: status, label or category.

get group() set group(value)
Type
string

zoom

accessormmo-kanban.jhs:359

The timeline's scale: day, week or month.

get zoom() set zoom(value)
Type
string

readonly

accessormmo-kanban.jhs:375

Whether nothing can be moved or edited.

get readonly() set readonly(value)
Type
boolean

lenient

accessormmo-kanban.jhs:388

Whether a move breaking a constraint is made anyway (with a warning).

get lenient() set lenient(value)
Type
boolean

busy

accessormmo-kanban.jhs:400

Whether the board waits on its data, or on a save: true, or what it waits on ('Saving to GitHub…').

get busy() set busy(value)
Type
boolean

data

accessormmo-kanban.jhs:470

The board's data: a copy of its statuses, labels, categories and tickets.

get data() set data(value)
Type
object

discussion

accessormmo-kanban.jhs:483

Where the tickets' discussions are: {load, add, update, remove} (the class's documentation), or null for none.

get discussion() set discussion(value)
Type
?object

Methods126

prompts(text)

staticmmo-kanban.jhs:182

The AI prompts of a ticket's description: as MMO.core.markdown reads a fence, and KanbanPrompts::Extract on the server -- a block nested in another fence is text.

static prompts(text)
textstring- markdown
returnsArray<{agent: string, info: string, prompt: string, line: number>} in its order; line from 1
customElements.get('mmo-kanban').prompts('```claude\nFix it\n```'); // [{agent: 'claude', info: '', prompt: 'Fix it', line: 1}]

isSaving(id)

mmo-kanban.jhs:426

Whether a ticket waits on a save: it cannot be changed until it is over (busy).

isSaving(id)
idstring
returnsboolean

refuseSaving(id)

mmo-kanban.jhs:431

refuse a change of a ticket being saved, saying so: true when refused

refuseSaving(id)
id

drawLocks()

mmo-kanban.jhs:438

show which tickets are being saved: their cards, rows and bars, and their editors (busy)

drawLocks()

load(data)

mmo-kanban.jhs:504

Replace the board's data.

load(data)
dataobject- {statuses, labels, categories, tickets}; a list left out keeps the current one
returnsvoid

configure(lists)

mmo-kanban.jhs:529

Set the board's statuses, labels and categories, from lists of objects or of names.

configure(lists)
listsobject- {statuses, labels, categories}; a list left out is kept
returnsvoid

lookOf(item)

mmo-kanban.jhs:556
lookOf(item)
itemobject- a status, a label or a category, as given
returnsobjectits color and icon, the ones it has

learnLabels(tickets)

mmo-kanban.jhs:564

the labels used by tickets and not known yet join the board's

learnLabels(tickets)
tickets

normalizeTicket(t)

mmo-kanban.jhs:585

Make a ticket the board can use, whatever it was given.

normalizeTicket(t)
tobject
returnsobject

normalizeConstraint(node)

mmo-kanban.jhs:616

Make a condition tree the board can use: an array is an AND, an unknown type is dropped.

normalizeConstraint(node)
node*
returns?object

isAncestor(ancestor, id)

mmo-kanban.jhs:664

Whether a ticket is an ancestor of another (or the same).

isAncestor(ancestor, id)
ancestorstring
idstring
returnsboolean

defaultStatus()

mmo-kanban.jhs:679

the status of a new ticket: the first that is not the backlog, when there is one before work starts

defaultStatus()

toDay(value)

mmo-kanban.jhs:687

────────────────────────────────────────────── Dates: days are 'YYYY-MM-DD' strings, events are local 'YYYY-MM-DDTHH:MM:SS' stamps ──────────────────────────────────────────────

toDay(value)
value

compareTime(a, b)

mmo-kanban.jhs:721

Compare two times at the precision of the less precise: a day against a stamp compares the days.

compareTime(a, b)
a
b
returns?number<0, 0, >0, or null when one is unknown

world(tickets, now)

mmo-kanban.jhs:737

The world a constraint is checked in: tickets, their lookups and the time.

world(tickets, now = this.now())
ticketsobject[]
now = this.now()
returnsobject

evaluate(node, b, world, mode)

mmo-kanban.jhs:759

Evaluate a condition tree for a ticket.

evaluate(node, b, world, mode = 'state')In `state` mode, against what happened (columns and stamps): is it kept, broken, or still open? In `plan` mode, against the planned `start` / `end` dates.
node?object- the condition
bobject- the ticket holding it
worldobject- world
mode = 'state'string- state or plan
returns?booleantrue, false, or null when it cannot be told (yet)

stateTicket(type, a, b)

mmo-kanban.jhs:798

a condition on another ticket, against what happened

stateTicket(type, a, b)
type
a
b

planTicket(type, a, b)

mmo-kanban.jhs:819

a condition on another ticket, against the planned dates

planTicket(type, a, b)
type
a
b

stateDate(node, b, world)

mmo-kanban.jhs:839

a condition on a date, against what happened: an event not yet happened may still keep it

stateDate(node, b, world)
node
b
world

planDate(node, b)

mmo-kanban.jhs:855

a condition on a date, against the planned dates

planDate(node, b)
node
b

fullConstraint(t, world)

mmo-kanban.jhs:867

A ticket's whole constraint: its own, and being part of its parent.

fullConstraint(t, world)
tobject
worldobject
returns?object

violations(tickets, now)

mmo-kanban.jhs:880

The tickets whose constraint is broken.

violations(tickets, now)
ticketsobject[]
nowstring
returnsMap<string, string>id => why

attempt(mutate)

mmo-kanban.jhs:897

Try a change on a copy of the tickets: what it would break.

attempt(mutate)
mutatefunction(object[], string):void- changes the copy it is given, at the time it is given
returnsnext: object[], broken: string[]}

commit(mutate, moved)

mmo-kanban.jhs:917

Make a change, unless it breaks a constraint (and the board is not lenient).

commit(mutate, moved = [])
mutatefunction(object[], string):void
moved = []string[]- the tickets it moves, which their fields do not say
returnsok: boolean, reasons: string[]}

whyBlocked(t)

mmo-kanban.jhs:950

Why a ticket cannot go to its next step (start, or be done), if it cannot.

whyBlocked(t)
tobject
returnsstring[]the constraints it would break, empty when it can

describe(node, world, nested)

mmo-kanban.jhs:968

Describe a condition tree in words.

describe(node, world = this.world(this.tickets), nested = false)
node?object
world = this.world(this.tickets)object
nested = falseboolean- whether it sits in another, which a group is parenthesised in
returnsstring

failing(node, b, world, mode)

mmo-kanban.jhs:998

Describe what makes a condition tree false: the items of an AND that are, the rest whole.

failing(node, b, world, mode = 'state')
node
b
world
mode = 'state'
returnsstring[]

leaves(node, negated, alternative, found)

mmo-kanban.jhs:1014

The conditions of a tree that name another ticket or a date, with how they are reached.

leaves(node, negated = false, alternative = false, found = [])
node?object
negated = false
alternative = false
found = []
returnsobject[][{node, negated, alternative}]

prune(node, id)

mmo-kanban.jhs:1029

Remove the conditions naming a ticket.

prune(node, id)
node
id
returns?objectwhat is left of the tree

setStatus(t, status, now)

mmo-kanban.jhs:1055

Give a ticket a status, stamping when it started and was done.

setStatus(t, status, now)
tobject- the ticket, changed in place
statusstring
nowstring

moveTicket(id, group, to, beforeId, from)

mmo-kanban.jhs:1080

Drop a ticket in a column of the board: give it that column's status, label or category, and put it before another ticket.

moveTicket(id, group, to, beforeId = null, from = null)
idstring
groupstring- what the columns are made from: status, label or category
to?string- the column: a status, a label or a category id; null for "no label/category"
beforeId = null?string- the ticket it goes before, null for the end
from = null?string- the label it leaves, for label (a ticket is in a column per label it has)
returnsok: boolean, reasons: string[]}

addTicket(ticket)

mmo-kanban.jhs:1139

Add a ticket.

addTicket(ticket)
ticketobject- its fields; status defaults to the first "not started" one after the backlog
returnsok: boolean, reasons: string[], id: string}

updateTicket(id, changes)

mmo-kanban.jhs:1154

Change a ticket's fields (a new status stamps it as a move does).

updateTicket(id, changes)
idstring
changesobject
returnsok: boolean, reasons: string[]}

removeTicket(id)

mmo-kanban.jhs:1185

Remove a ticket: its sub-tickets go to its parent, and the conditions naming it go.

removeTicket(id)
idstring
returnsok: boolean, reasons: string[]}

look(kind, item)

mmo-kanban.jhs:1261

How a status, a label or a category looks: its own colour and icon, or its kind's.

look(kind, item)
kindstring- status, label or category
item?object
returnstone: string, icon: ?string} a css colour, and an icon name (a label has none unless given one)

labelItem(id)

mmo-kanban.jhs:1283
labelItem(id)
id
returnsobjecta label of the board, or what stands for one it does not know

badge(kind, item, text)

mmo-kanban.jhs:1294

A status, a label or a category as a pill, in its colour, with its icon.

badge(kind, item, text = null)
kindstring- status, label or category
itemobject
text = null?string- what it says, its label by default
returnsHTMLElement

person(person, css)

mmo-kanban.jhs:1306

Someone, as a picture (or initials) and a name.

person(person, css = '')
personobject- {id, label, avatar}
css = ''string
returnsHTMLElement

assignable()

mmo-kanban.jhs:1316
assignable()
returnsobject[]who a ticket can be assigned to: the board's people, and whoever tickets are assigned to

icon(name, css, title)

mmo-kanban.jhs:1324
icon(name, css = 'xs', title = null)
name
css = 'xs'
title = null

stateIcon(t)

mmo-kanban.jhs:1331

the state of a ticket, as an icon: broken, blocked, done

stateIcon(t)
t

columnsFor(group)

mmo-kanban.jhs:1346

The board's columns for a grouping: one per status, label or category — and, for labels and categories, one first for the tickets that have none.

columnsFor(group)
groupstring- status, label or category
returnsobject[][{key, id, label, css, limit, defaults, has(ticket), tone, icon}]

mark(col)

mmo-kanban.jhs:1431

what stands for a column before its name: its icon, in its colour, or a dot

mark(col)
col

scrollToColumn(id)

mmo-kanban.jhs:1436

bring a column of the board into sight

scrollToColumn(id)
id

markVisibleColumn()

mmo-kanban.jhs:1443

the strip of columns says which one is in sight

markVisibleColumn()

onCardKey(event, t, col)

mmo-kanban.jhs:1554

Enter opens a card; Alt+arrows move it across columns and within its column

onCardKey(event, t, col)
event
t
col

placePlaceholder(cards, clientY)

mmo-kanban.jhs:1583

the gap a dragged card shows where it would land, in a column at a height of the screen

placePlaceholder(cards, clientY)
cards
clientY

placeholderNext()

mmo-kanban.jhs:1604

the ticket the dragged card goes before: the card after the gap, null for the end

placeholderNext()

wireDrop(cards, group, col)

mmo-kanban.jhs:1617

a column's cards take the dragged one, at the place the pointer shows

wireDrop(cards, group, col)
cards
group
col

wireTouchDrag(card, t, col)

mmo-kanban.jhs:1646

Drag a card with a finger: a long press picks it up (a quick swipe still scrolls), it follows the finger, the board scrolls when it nears an edge, and it drops in the column under it — or on a column's name in the strip above a narrow board.

wireTouchDrag(card, t, col)
card
t
col

startTouchDrag(card, t, col, start)

mmo-kanban.jhs:1688
startTouchDrag(card, t, col, start)
card
t
col
start

scrollParent()

mmo-kanban.jhs:1766

the box the page scrolls in, through the shadow roots and slots this element sits in

scrollParent()

rows(world)

mmo-kanban.jhs:1816

The tickets in tree order, as rows: the filtered ones and their ancestors, below an unfolded parent.

rows(world)
world
returnsobject[][{ticket, depth, hasChildren}]

route(from, to, kind)

mmo-kanban.jhs:2139

The path of an arrow between two bars, in right angles.

route(from, to, kind)
fromnumber[]- [x, y]
tonumber[]- [x, y]
kindstring- fs end to start, ff end to end, ss start to start
returnsstring

wireBars(svg, bars, px)

mmo-kanban.jhs:2159

a bar is moved by its body and resized by its ends, a day at a time

wireBars(svg, bars, px)
svg
bars
px

edit(id, defaults)

mmo-kanban.jhs:2230

Open a ticket, or a new one, in the side panel of the application (<mmo-app>): a tab per ticket, the same ticket twice shows its tab again. Outside an application, in a dialog.

edit(id, defaults = {})
id?string- null for a new ticket
defaults = {}object- the fields of a new ticket (status, parent…)
returnsvoid

usesPanels()

mmo-kanban.jhs:2271

whether tickets open in the application's side panel (its tabbed one) rather than in a dialog

usesPanels()

panelKey(id)

mmo-kanban.jhs:2276

a panel's key: the same ticket of the same board is one panel

panelKey(id)
id

refreshEditors()

mmo-kanban.jhs:2288

Draw the open editors again after a change of the board: the ones with changes not saved are left as they are, the ones whose ticket is gone close.

refreshEditors()

lockEditor(editor)

mmo-kanban.jhs:2308

An editor whose ticket is being saved waits for it: its fields cannot be reached, and it says why (busy).

lockEditor(editor)Its discussion, saved apart, stays open.
editorobject

closeEditor(editor)

mmo-kanban.jhs:2319

close an editor: its panel, or the dialog

closeEditor(editor)
editor

renderEditor(editor)

mmo-kanban.jhs:2331

Draw an editor: its tabs (the ticket, its discussion), and the ticket's form -- its text on the left, its properties on the right, picked from dropdowns. The form is drawn again on every refresh of the board; the discussion only once (discussionSection), so what is being typed in it stays.

renderEditor(editor)
editorobject
returnsvoid

drawTabs(editor)

mmo-kanban.jhs:2535

The tabs of an editor -- the ticket, its discussion -- drawn again when the count of comments changes; only an existing ticket with a discussion has them.

drawTabs(editor)
editorobject
returnsvoid

isCompact(editor)

mmo-kanban.jhs:2563

whether an editor is too narrow for two columns

isCompact(editor)
editor

watchEditorWidth(editor)

mmo-kanban.jhs:2569

an editor goes to one column, and back, as its panel or its dialog is resized

watchEditorWidth(editor)
editor

dropdown(box, button, pop, onOpen)

mmo-kanban.jhs:2587

A popover that opens under its button, and closes on Escape or on a click anywhere else.

dropdown(box, button, pop, onOpen)
boxHTMLElement- holds the button and the popover
buttonHTMLElement
popHTMLElement
onOpenfunction():void- fills the popover, each time it opens
returnsopen: function():void, close: function(boolean=):void, isOpen: function():boolean}

picker(spec)

mmo-kanban.jhs:2632

A dropdown to pick a value, or several: a button showing what is picked, and a list to pick from, filtered by what is typed in it.

picker(spec)
specobject- options(): [{value, text, render()}]: text is what the filter searches, render() draws the option - value: what is picked, an array when multiple - multiple: several can be picked; the list stays open - none: what "nothing" is called, when nothing is allowed (single only) - custom: a typed value that is not in the list can be picked, offered as {custom} "value" - placeholder: what the button says when nothing is picked - footer(): something to show under the list - readonly, label (for assistive technologies), onChange(value)
returnsHTMLElement

lookPicker(kind, item)

mmo-kanban.jhs:2744

The colour and the icon of a status, a label or a category, in the board's settings: a button showing them, and a popover to pick them.

lookPicker(kind, item)
kindstring- status, label or category
itemobject- changed in place
returnsHTMLElement

descriptionEditor(t, ro, bind)

mmo-kanban.jhs:2801

The description of a ticket: markdown, in <mmo-markdown-editor> (a plain textarea when the page does not have it). A ticket that has one opens on its preview, to be read; # suggests the board's tickets, @ the people it is assigned to.

descriptionEditor(t, ro, bind)
tobject- the ticket being edited, changed in place
roboolean
bindfunction- binds a plain control to a field of the ticket
returnsHTMLElement

addTriggers(editor, id, people)

mmo-kanban.jhs:2826

# suggests the board's tickets, @ the people: the ones tickets are assigned to, and more.

addTriggers(editor, id, people = () => [])
editorHTMLElement- an <mmo-markdown-editor>
id?string- the ticket written about, which # leaves out
people = () => []function():string[]- more people to suggest
returnsvoid

discussionSection(editor)

mmo-kanban.jhs:2853

Start the discussion of an editor's ticket: its state lives in editor.talk, its section is drawn by

discussionSection(editor)
See
drawDiscussion, and its comments are loaded now.
editorobject
returnsHTMLElementthe section

loadDiscussion(editor)

asyncmmo-kanban.jhs:2882

(Re)load the comments of an editor's ticket.

async loadDiscussion(editor)
editorobject
returnsPromise<void>

normalizeComment(c)

mmo-kanban.jhs:2902
normalizeComment(c)
cobject- a comment, as the discussion gave it
returnsobjecta comment the board can use, whatever it was given

countComments(editor)

mmo-kanban.jhs:2918

the card says how many comments its ticket has

countComments(editor)
editor

drawDiscussion(editor)

mmo-kanban.jhs:2933

Draw an editor's discussion: its header (the count, the order, reload), its comments, and the editor of a new one.

drawDiscussion(editor)
editorobject
returnsvoid

commentList(editor)

mmo-kanban.jhs:2981

The comments, in the viewer's order: by date, or in threads -- a reply under what it answers, a reply to a comment that is gone starting a thread of its own.

commentList(editor)
editorobject
returnsHTMLElement

commentNode(editor, c, byId, sayParent)

mmo-kanban.jhs:3028

A comment: who wrote it and when, what it says, and what the viewer can do with it.

commentNode(editor, c, byId, sayParent)
editorobject
cobject- the comment
byIdMap<string, object>- the discussion's comments
sayParentboolean- whether to say which comment it answers
returnsHTMLElement

commentEditor(editor, kind, target)

mmo-kanban.jhs:3101

The editor of a comment: a new one, a reply, or a change of one's own.

commentEditor(editor, kind, target)
editorobject
kindstring- new, reply or edit
target?string- the comment answered or changed
returnsHTMLElement

focusComposer(editor, key)

mmo-kanban.jhs:3149

put the caret in a comment's editor, once it is drawn

focusComposer(editor, key)
editor
key

sendComment(editor, kind, target)

asyncmmo-kanban.jhs:3166

Send what a comment's editor holds: a new comment, a reply, or a change.

async sendComment(editor, kind, target)
editorobject
kindstring- new, reply or edit
target?string
returnsPromise<void>

removeComment(editor, c)

asyncmmo-kanban.jhs:3208

Delete one's own comment, once asked: its replies stay, answering a deleted comment.

async removeComment(editor, c)
editorobject
cobject
returnsPromise<void>

showComment(editor, id)

mmo-kanban.jhs:3228

scroll to a comment of the discussion, and make it flash

showComment(editor, id)
editor
id

storedOrder()

mmo-kanban.jhs:3243
storedOrder()
returnsstringthe order the viewer picked last, threads unless they did

initials(name)

mmo-kanban.jhs:3268
initials(name)
name
returnsstringwhat stands for someone without a picture: MM for marc-mouret

conditionEditor(t, node, set, redraw, preview, root)

mmo-kanban.jhs:3282

The editor of one condition, and of the ones it holds.

conditionEditor(t, node, set, redraw, preview, root = false)
tobject- the ticket being edited
node?object
setfunction(?object):void- puts a new node in its place, null removes it
redrawfunction():void- draws the whole tree again, after a change of its shape
previewHTMLElement- the description to update after a change of a value
root = falseboolean
returnsHTMLElement

convertCondition(t, node, type)

mmo-kanban.jhs:3386

a condition turned into another type, keeping what both share

convertCondition(t, node, type)
t
node
type

save(t, isNew)

mmo-kanban.jhs:3403

Save a ticket being edited.

save(t, isNew)
tobject- the edited copy
isNewboolean
returnsok: boolean, reasons: string[], id: ?string}

configureBoard(lists)

mmo-kanban.jhs:3428

Change the board's statuses, labels and categories. A ticket whose status goes takes the first status left of the same type; a label or a category that goes leaves its tickets. A status changing type stamps its tickets as a move would, so the change is refused when it breaks a constraint.

configureBoard(lists)
listsobject- {statuses, labels, categories}, each [{id, label}] (a status also has type and limit); an item without id is new; a list left out is kept
returnsok: boolean, reasons: string[]}

editSettings()

mmo-kanban.jhs:3491

Open the board's settings: its statuses, labels and categories.

editSettings()
returnsvoid

notify(title, lines, tone)

mmo-kanban.jhs:3602

Say something for a while, under the toolbar.

notify(title, lines = [], tone = 'info')
titlestring
lines = []string[]
tone = 'info'string- bad, warn or info

Slots1

(default)

mmo-kanban.jhs

a <script type="application/json"> holding the board's data

<slot>