@basementuniverse/kanbn 1.1.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/coverage-summary.json +31 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +146 -0
- package/coverage/kanbn/index.html +116 -0
- package/coverage/kanbn/index.js.html +217 -0
- package/coverage/kanbn/src/board.js.html +646 -0
- package/coverage/kanbn/src/controller/add.js.html +1729 -0
- package/coverage/kanbn/src/controller/archive.js.html +277 -0
- package/coverage/kanbn/src/controller/board.js.html +217 -0
- package/coverage/kanbn/src/controller/burndown.js.html +607 -0
- package/coverage/kanbn/src/controller/comment.js.html +397 -0
- package/coverage/kanbn/src/controller/edit.js.html +2920 -0
- package/coverage/kanbn/src/controller/find.js.html +1597 -0
- package/coverage/kanbn/src/controller/gantt.js.html +1342 -0
- package/coverage/kanbn/src/controller/help.js.html +115 -0
- package/coverage/kanbn/src/controller/history.js.html +400 -0
- package/coverage/kanbn/src/controller/index.html +431 -0
- package/coverage/kanbn/src/controller/init.js.html +511 -0
- package/coverage/kanbn/src/controller/move.js.html +514 -0
- package/coverage/kanbn/src/controller/remove-all.js.html +226 -0
- package/coverage/kanbn/src/controller/remove.js.html +307 -0
- package/coverage/kanbn/src/controller/rename.js.html +397 -0
- package/coverage/kanbn/src/controller/restore.js.html +268 -0
- package/coverage/kanbn/src/controller/sort.js.html +1318 -0
- package/coverage/kanbn/src/controller/sprint.js.html +361 -0
- package/coverage/kanbn/src/controller/status.js.html +310 -0
- package/coverage/kanbn/src/controller/task.js.html +244 -0
- package/coverage/kanbn/src/controller/validate.js.html +367 -0
- package/coverage/kanbn/src/controller/version.js.html +100 -0
- package/coverage/kanbn/src/index.html +191 -0
- package/coverage/kanbn/src/main.js.html +9787 -0
- package/coverage/kanbn/src/parse-index.js.html +1063 -0
- package/coverage/kanbn/src/parse-markdown.js.html +202 -0
- package/coverage/kanbn/src/parse-task.js.html +2191 -0
- package/coverage/kanbn/src/utility.js.html +616 -0
- package/coverage/lcov.info +11837 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/tmp/coverage-214292-1787777191526-0.json +1 -0
- package/coverage/tmp/coverage-214293-1787777184569-0.json +1 -0
- package/docs/advanced-configuration.md +25 -0
- package/docs/commands/add.txt +19 -0
- package/docs/commands/archive.txt +5 -0
- package/docs/commands/board.txt +8 -1
- package/docs/commands/boards.txt +34 -0
- package/docs/commands/burndown.txt +8 -4
- package/docs/commands/comment.txt +5 -0
- package/docs/commands/edit.txt +27 -0
- package/docs/commands/find.txt +56 -1
- package/docs/commands/gantt.txt +5 -0
- package/docs/commands/help.txt +2 -0
- package/docs/commands/history.txt +5 -0
- package/docs/commands/init.txt +13 -0
- package/docs/commands/move.txt +10 -1
- package/docs/commands/remove.txt +12 -1
- package/docs/commands/rename.txt +5 -0
- package/docs/commands/restore.txt +6 -0
- package/docs/commands/sort.txt +18 -2
- package/docs/commands/sprint.txt +9 -0
- package/docs/commands/status.txt +10 -1
- package/docs/commands/task.txt +5 -0
- package/docs/commands/validate.txt +27 -0
- package/docs/filtering-and-sorting.md +218 -0
- package/docs/index-structure.md +94 -19
- package/docs/index.md +28 -2
- package/docs/migration-2.0.md +92 -0
- package/docs/multiple-boards.md +258 -0
- package/docs/quick-start.md +57 -1
- package/docs/task-structure.md +33 -3
- package/docs/views.md +229 -0
- package/example/README.md +93 -0
- package/example/advanced/.kanbn/index.md +26 -0
- package/example/advanced/.kanbn/tasks/add-rate-limiting.md +18 -0
- package/example/advanced/.kanbn/tasks/add-sso-support.md +15 -0
- package/example/advanced/.kanbn/tasks/migrate-to-new-cdn.md +14 -0
- package/example/advanced/.kanbn/tasks/refresh-marketing-site.md +14 -0
- package/example/advanced/.kanbn/tasks/rotate-api-keys.md +15 -0
- package/example/advanced/.kanbn/tasks/split-billing-service.md +17 -0
- package/example/advanced/.kanbn/tasks/upgrade-node-runtime.md +19 -0
- package/example/advanced/kanbn.yml +59 -0
- package/example/boards/.kanbn/design.md +31 -0
- package/example/boards/.kanbn/index.md +44 -0
- package/example/boards/.kanbn/tasks/add-usage-alert-emails.md +19 -0
- package/example/boards/.kanbn/tasks/build-tenant-settings-page.md +43 -0
- package/example/boards/.kanbn/tasks/create-organization-switcher.md +44 -0
- package/example/boards/.kanbn/tasks/design-onboarding-checklist.md +22 -0
- package/example/boards/.kanbn/tasks/refresh-marketing-site.md +28 -0
- package/example/boards/.kanbn/tasks/ship-billing-portal.md +29 -0
- package/example/views/.kanbn/index.md +140 -0
- package/example/views/.kanbn/tasks/add-audit-log-export.md +18 -0
- package/example/views/.kanbn/tasks/add-dark-mode-toggle.md +16 -0
- package/example/views/.kanbn/tasks/build-webhook-retry-queue.md +15 -0
- package/example/views/.kanbn/tasks/fix-avatar-upload-crash.md +13 -0
- package/example/views/.kanbn/tasks/fix-expired-session-redirect.md +15 -0
- package/example/views/.kanbn/tasks/investigate-offline-mode.md +12 -0
- package/example/views/.kanbn/tasks/rewrite-search-indexer.md +12 -0
- package/example/views/.kanbn/tasks/tidy-up-settings-copy.md +12 -0
- package/package.json +12 -9
- package/routes/add.json +35 -11
- package/routes/archive.json +10 -2
- package/routes/board.json +11 -3
- package/routes/boards.json +30 -0
- package/routes/burndown.json +23 -7
- package/routes/comment.json +14 -4
- package/routes/edit.json +33 -10
- package/routes/find.json +37 -12
- package/routes/gantt.json +20 -6
- package/routes/history.json +39 -25
- package/routes/init.json +3 -1
- package/routes/move.json +22 -6
- package/routes/remove.json +15 -4
- package/routes/rename.json +11 -3
- package/routes/restore.json +8 -2
- package/routes/sort.json +35 -11
- package/routes/sprint.json +14 -4
- package/routes/status.json +23 -7
- package/routes/task.json +10 -2
- package/routes/validate.json +19 -4
- package/skills/kanbn-plan/SKILL.md +10 -1
- package/skills/kanbn-plan/references/index-structure.md +1 -0
- package/skills/kanbn-plan/references/task-structure.md +2 -0
- package/skills/kanbn-replan/SKILL.md +6 -1
- package/src/board.js +2 -1
- package/src/controller/add.js +52 -46
- package/src/controller/archive.js +8 -4
- package/src/controller/board.js +9 -4
- package/src/controller/boards.js +140 -0
- package/src/controller/burndown.js +9 -5
- package/src/controller/comment.js +9 -5
- package/src/controller/edit.js +74 -17
- package/src/controller/find.js +12 -9
- package/src/controller/gantt.js +8 -4
- package/src/controller/history.js +8 -4
- package/src/controller/init.js +39 -4
- package/src/controller/move.js +69 -15
- package/src/controller/remove.js +34 -11
- package/src/controller/rename.js +8 -4
- package/src/controller/restore.js +23 -8
- package/src/controller/sort.js +19 -3
- package/src/controller/sprint.js +31 -7
- package/src/controller/status.js +20 -4
- package/src/controller/task.js +22 -9
- package/src/controller/validate.js +139 -23
- package/src/git-user-name.js +19 -0
- package/src/main.d.ts +184 -6
- package/src/main.js +1642 -221
- package/src/parse-index.js +17 -0
- package/src/parse-markdown.js +3 -1
- package/src/parse-task.js +44 -1
- package/src/utility.js +166 -1
- package/PLANS.md +0 -9
- /package/example/{.kanbn → basic/.kanbn}/index.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/add-basic-activity-feed.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/add-passwordless-login-option.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/add-usage-alert-email-thresholds.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/build-email-template-system.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/build-invoice-download-endpoint.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/build-tenant-settings-page.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/create-organization-switcher.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/create-sandbox-environment-provisioner.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/create-self-serve-cancellation-flow.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/define-product-pricing-strategy.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/design-onboarding-checklist.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/design-team-invite-expiry-flow.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-data-retention-policy-jobs.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-feature-flags-foundation.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-project-creation-wizard.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-stripe-webhook-signature-check.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-team-permissions-ui.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-user-signup-and-login.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/integrate-crm-lead-sync.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/legal-review-terms-and-privacy.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/migrate-legacy-events-to-new-schema.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/optimize-dashboard-first-load.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/prototype-report-export-scheduler.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/publish-internal-qa-checklist.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/setup-ci-pipeline.md +0 -0
package/docs/task-structure.md
CHANGED
|
@@ -64,7 +64,7 @@ There should be a single level-1 heading at the top of the markdown body contain
|
|
|
64
64
|
|
|
65
65
|
The task description should appear below the title. The description can be of any length and can contain markdown.
|
|
66
66
|
|
|
67
|
-
The following level-2 headings are reserved for special purposes
|
|
67
|
+
The following level-2 headings are reserved for special purposes (`## Metadata`, `## Sub-tasks`, `## Relations`, `## Comments` and `## History`). Any other level-2 heading is treated as part of the description.
|
|
68
68
|
|
|
69
69
|
## Metadata
|
|
70
70
|
|
|
@@ -106,6 +106,8 @@ The name of the user this task is assigned to.
|
|
|
106
106
|
|
|
107
107
|
The amount of progress for this task. This should be between 0 (not started) and 1 (complete).
|
|
108
108
|
|
|
109
|
+
Progress is not derived from sub-tasks. If the task has no `progress` value it counts as 0, and a task that is considered complete always counts as 1 regardless of this value. A task is considered complete if its [`completedField`](index-structure.md#completedfield) — `completed` by default — has a date. The column a task sits in doesn't make it complete; see [Index Structure](index-structure.md#completedcolumns).
|
|
110
|
+
|
|
109
111
|
### `started`
|
|
110
112
|
|
|
111
113
|
The date and time that the task was started.
|
|
@@ -126,6 +128,14 @@ An optional date and time used as a preferred start point for gantt scheduling.
|
|
|
126
128
|
|
|
127
129
|
An optional date and time used as a preferred finish point for gantt scheduling.
|
|
128
130
|
|
|
131
|
+
### Custom fields
|
|
132
|
+
|
|
133
|
+
Any other metadata field is preserved but otherwise ignored, unless it is declared in the [`customFields`](index-structure.md#customfields) project option. Declared custom fields can be set with `kanbn add` and `kanbn edit`, searched with `kanbn find`, sorted with `kanbn sort`, filtered in [views](views.md), and interpolated into the board's [`taskTemplate`](index-structure.md#tasktemplate).
|
|
134
|
+
|
|
135
|
+
### Workload
|
|
136
|
+
|
|
137
|
+
A task's workload is not stored in the file - it is calculated from the task's `tags`. It is the sum of the values of its [workload tags](index-structure.md#taskworkloadtags), or `defaultTaskWorkload` if it has none.
|
|
138
|
+
|
|
129
139
|
## Sub-tasks
|
|
130
140
|
|
|
131
141
|
This should be a list of strings. Each string can optionally start with `[ ]` (incomplete) or `[x]` (completed).
|
|
@@ -134,8 +144,7 @@ This should be a list of strings. Each string can optionally start with `[ ]` (i
|
|
|
134
144
|
|
|
135
145
|
This should be a list of links to other task files. The link text can optionally begin with a relation type.
|
|
136
146
|
|
|
137
|
-
The `depends-on` relation type is reserved for gantt scheduling. It means the current task should not start until the
|
|
138
|
-
linked task has finished.
|
|
147
|
+
The `depends-on` relation type is reserved for gantt scheduling. It means the current task should not start until the linked task has finished.
|
|
139
148
|
|
|
140
149
|
## Comments
|
|
141
150
|
|
|
@@ -148,6 +157,8 @@ An optional array of structured lifecycle events used for richer timeline report
|
|
|
148
157
|
Supported event types:
|
|
149
158
|
|
|
150
159
|
- `created`: requires `date` and `column`
|
|
160
|
+
- `added`: requires `date` and `column` — the task joined a board
|
|
161
|
+
- `removed`: requires `date` — the task left a board
|
|
151
162
|
- `moved`: requires `date`, `fromColumn`, `toColumn`
|
|
152
163
|
- `progress`: requires `date`, `fromProgress`, `toProgress`
|
|
153
164
|
- `archived`: requires `date`, `fromColumn`
|
|
@@ -158,3 +169,22 @@ Notes:
|
|
|
158
169
|
- History entries are stored in the reserved `## History` section as list items.
|
|
159
170
|
- `date` should be an ISO timestamp.
|
|
160
171
|
- Additional fields (for example `author`) are preserved if present.
|
|
172
|
+
- An event type this version of Kanbn doesn't recognise is preserved and skipped rather than rejected, so a task file written by a newer version stays readable.
|
|
173
|
+
|
|
174
|
+
### Board attribution
|
|
175
|
+
|
|
176
|
+
In a workspace with [multiple boards](multiple-boards.md), an event carries an optional `board` key naming the board it happened on:
|
|
177
|
+
|
|
178
|
+
```markdown
|
|
179
|
+
## History
|
|
180
|
+
|
|
181
|
+
- type: moved
|
|
182
|
+
date: 2026-07-14T09:12:00.000Z
|
|
183
|
+
fromColumn: Ideas
|
|
184
|
+
toColumn: Designing
|
|
185
|
+
board: design
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Events on the **main board carry no `board` key**, so a single-board workspace writes exactly the history it always has, and every existing task file stays valid.
|
|
189
|
+
|
|
190
|
+
`archived` and `progress` events are never board-scoped: archiving removes a task from every board, and progress is a property of the task rather than of a board.
|
package/docs/views.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Views
|
|
2
|
+
|
|
3
|
+
A view is a saved board layout. Views let you re-arrange, filter and group the same set of tasks without changing the index columns or moving any tasks around.
|
|
4
|
+
|
|
5
|
+
Views are defined in the `views` project option (index front matter, or `kanbn.json` / `kanbn.yml` — see [Advanced Configuration](advanced-configuration.md)) and are selected with:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
kanbn board --view "view name"
|
|
9
|
+
kanbn board -v "view name"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If the named view doesn't exist, `kanbn board` reports an error. Running `kanbn board` with no `--view` option always uses the [default board](#the-default-board), even if a view called `default` exists.
|
|
13
|
+
|
|
14
|
+
A working example lives in [`example/views`](../example/views).
|
|
15
|
+
|
|
16
|
+
## Structure
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
views:
|
|
20
|
+
- name: 'View name' # required
|
|
21
|
+
filters: {} # optional, applied to every task before columns and lanes
|
|
22
|
+
columns: # optional, at least 1 entry if present
|
|
23
|
+
- name: 'Column name' # required, this is the board heading
|
|
24
|
+
filters: {} # optional
|
|
25
|
+
sorters: [] # optional
|
|
26
|
+
lanes: # optional
|
|
27
|
+
- name: 'Lane name' # required
|
|
28
|
+
filters: {} # optional
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Property | Where | Required | Description |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| `name` | view | yes | The name passed to `kanbn board --view`. If two views share a name, the first one wins |
|
|
34
|
+
| `filters` | view | no | A [filter set](filtering-and-sorting.md#filters) applied to all tasks before anything else |
|
|
35
|
+
| `columns` | view | no | The board columns, left to right. Defaults to the index columns |
|
|
36
|
+
| `columns[].name` | column | yes | The column heading. It does **not** have to be an index column name |
|
|
37
|
+
| `columns[].filters` | column | no | A [filter set](filtering-and-sorting.md#filters) deciding which tasks appear in this column |
|
|
38
|
+
| `columns[].sorters` | column | no | A [sorter list](filtering-and-sorting.md#sorters) for tasks in this column |
|
|
39
|
+
| `lanes` | view | no | Horizontal swimlanes, top to bottom. Defaults to a single lane called "All tasks" |
|
|
40
|
+
| `lanes[].name` | lane | yes | The lane label |
|
|
41
|
+
| `lanes[].filters` | lane | no | A [filter set](filtering-and-sorting.md#filters) applied to every column in this lane |
|
|
42
|
+
|
|
43
|
+
Anything else is ignored. In particular there is **no** `hidden` property on view columns — to leave a column out of a view, just don't list it.
|
|
44
|
+
|
|
45
|
+
## How a view is built
|
|
46
|
+
|
|
47
|
+
For every lane, and for every column in that lane, Kanbn takes the full list of tracked tasks and:
|
|
48
|
+
|
|
49
|
+
1. applies the view's root `filters` (if any);
|
|
50
|
+
2. merges the column's `filters` with the lane's `filters` and applies the result;
|
|
51
|
+
3. sorts what's left using the column's `sorters`.
|
|
52
|
+
|
|
53
|
+
The merge in step 2 is a shallow object merge with the **lane winning**: if a column and a lane both filter on the same field, the lane's value is used. Filters on different fields combine with AND.
|
|
54
|
+
|
|
55
|
+
Two consequences worth knowing:
|
|
56
|
+
|
|
57
|
+
- **A column with no `filters` shows every task**, in every lane, regardless of which index column the tasks are actually in. Column names in a view are just labels. If you want a view column to show one index column, filter on it explicitly:
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
- name: 'In Progress'
|
|
61
|
+
filters:
|
|
62
|
+
column: 'In Progress'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- **Views can overlap.** The same task can appear in more than one column or lane if it matches more than one filter set. That's often what you want (a "Bugs" and an "Overdue" column side by side), but it does mean a view is not necessarily a partition of the board.
|
|
66
|
+
|
|
67
|
+
## The default board
|
|
68
|
+
|
|
69
|
+
When a view doesn't define `columns`, Kanbn builds them from the index: one column per index column, in index order, each filtered to `column: <that column name>`, and [`hiddenColumns`](index-structure.md#hiddencolumns) removed.
|
|
70
|
+
|
|
71
|
+
`hiddenColumns` is only consulted when building this default column list. **A view that defines its own `columns` ignores `hiddenColumns` entirely**, so a view is also the way to see hidden columns:
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
views:
|
|
75
|
+
- name: 'everything'
|
|
76
|
+
columns:
|
|
77
|
+
- name: 'All tasks (including hidden columns)'
|
|
78
|
+
sorters:
|
|
79
|
+
- field: column
|
|
80
|
+
order: ascending
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
When a view doesn't define `lanes` (or defines an empty list), a single lane called "All tasks" is used.
|
|
84
|
+
|
|
85
|
+
## Lanes
|
|
86
|
+
|
|
87
|
+
A lane is a horizontal band across the whole board. Every column is rendered once per lane, filtered by that lane's filters. Lanes are for grouping the same columns by some secondary dimension: assignee, tag, workload, priority, and so on.
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
views:
|
|
91
|
+
- name: 'by-assignee'
|
|
92
|
+
columns:
|
|
93
|
+
- name: 'Backlog'
|
|
94
|
+
filters:
|
|
95
|
+
column: 'Backlog'
|
|
96
|
+
- name: 'In Progress'
|
|
97
|
+
filters:
|
|
98
|
+
column: 'In Progress'
|
|
99
|
+
- name: 'Done'
|
|
100
|
+
filters:
|
|
101
|
+
column: 'Done'
|
|
102
|
+
lanes:
|
|
103
|
+
- name: 'Ana'
|
|
104
|
+
filters:
|
|
105
|
+
assigned: '^Ana$'
|
|
106
|
+
- name: 'Ben'
|
|
107
|
+
filters:
|
|
108
|
+
assigned: '^Ben$'
|
|
109
|
+
- name: 'Unassigned'
|
|
110
|
+
filters:
|
|
111
|
+
assigned: '^$'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
╭──────────────────────┬──────────────────────┬──────────────────────╮
|
|
116
|
+
│Backlog │» In Progress │✓ Done │
|
|
117
|
+
├──────────────────────┼──────────────────────┼──────────────────────┤
|
|
118
|
+
│Ana │ │ │
|
|
119
|
+
├──────────────────────┼──────────────────────┼──────────────────────┤
|
|
120
|
+
│Add Audit Log Export │Fix Expired Session │Add Dark Mode Toggle │
|
|
121
|
+
│ │Redirect │ │
|
|
122
|
+
├──────────────────────┼──────────────────────┼──────────────────────┤
|
|
123
|
+
│Ben │ │ │
|
|
124
|
+
├──────────────────────┼──────────────────────┼──────────────────────┤
|
|
125
|
+
│Fix Avatar Upload │Build Webhook Retry │ │
|
|
126
|
+
│Crash │Queue │ │
|
|
127
|
+
├──────────────────────┼──────────────────────┼──────────────────────┤
|
|
128
|
+
│Unassigned │ │ │
|
|
129
|
+
├──────────────────────┼──────────────────────┼──────────────────────┤
|
|
130
|
+
│Rewrite Search │ │ │
|
|
131
|
+
│Indexer │ │ │
|
|
132
|
+
│ │ │ │
|
|
133
|
+
│Tidy Up Settings Copy │ │ │
|
|
134
|
+
╰──────────────────────┴──────────────────────┴──────────────────────╯
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Because `assigned` is a [string filter](filtering-and-sorting.md#string-filters) matched as a regex, `'^Ana$'` matches only tasks assigned to exactly "Ana" (a bare `Ana` would also match "Ana-Maria"), and `'^$'` matches unassigned tasks, whose `assigned` value is treated as an empty string.
|
|
138
|
+
|
|
139
|
+
*Note: a task that matches no lane filter simply isn't shown. Lanes don't have a catch-all.*
|
|
140
|
+
|
|
141
|
+
## Examples
|
|
142
|
+
|
|
143
|
+
All of these are in [`example/views/.kanbn/index.md`](../example/views/.kanbn/index.md) and can be run from the `example/views` directory.
|
|
144
|
+
|
|
145
|
+
### Group by workload
|
|
146
|
+
|
|
147
|
+
Columns as workload buckets, lanes splitting started from not-started work. Neither the columns nor the lanes correspond to index columns:
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
- name: 'workload'
|
|
151
|
+
columns:
|
|
152
|
+
- name: 'Small (1-2)'
|
|
153
|
+
filters:
|
|
154
|
+
workload: [1, 2]
|
|
155
|
+
- name: 'Medium (3-5)'
|
|
156
|
+
filters:
|
|
157
|
+
workload: [3, 5]
|
|
158
|
+
- name: 'Large (6+)'
|
|
159
|
+
filters:
|
|
160
|
+
workload: [6, 99]
|
|
161
|
+
lanes:
|
|
162
|
+
- name: 'Not started'
|
|
163
|
+
filters:
|
|
164
|
+
column: 'Backlog'
|
|
165
|
+
- name: 'Started'
|
|
166
|
+
filters:
|
|
167
|
+
column: 'In Progress'
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Root filter plus per-column sorting
|
|
171
|
+
|
|
172
|
+
The root `filters` narrows the whole view to bugs; the first column narrows further by due date and sorts by workload; the second column has no filters, so within this view it shows all bugs:
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
- name: 'triage'
|
|
176
|
+
filters:
|
|
177
|
+
tag: Bug
|
|
178
|
+
columns:
|
|
179
|
+
- name: 'Due before Aug 2026'
|
|
180
|
+
filters:
|
|
181
|
+
due:
|
|
182
|
+
- 2020-01-01
|
|
183
|
+
- 2026-08-01
|
|
184
|
+
sorters:
|
|
185
|
+
- field: workload
|
|
186
|
+
order: descending
|
|
187
|
+
- name: 'All bugs'
|
|
188
|
+
sorters:
|
|
189
|
+
- field: name
|
|
190
|
+
order: ascending
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Inspecting a view
|
|
194
|
+
|
|
195
|
+
`kanbn board --json` (with or without `--view`) prints the resolved board rather than rendering it, which is the quickest way to check that a view's filters do what you expect:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
kanbn board -v triage --json
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"headings": [
|
|
204
|
+
{ "name": "Due before Aug 2026", "heading": "^:^+Due before Aug 2026^:" },
|
|
205
|
+
{ "name": "All bugs", "heading": "^:^+All bugs^:" }
|
|
206
|
+
],
|
|
207
|
+
"lanes": [
|
|
208
|
+
{
|
|
209
|
+
"name": "All tasks",
|
|
210
|
+
"columns": [
|
|
211
|
+
[
|
|
212
|
+
{ "id": "fix-expired-session-redirect", "workload": 3, "...": "..." },
|
|
213
|
+
{ "id": "fix-avatar-upload-crash", "workload": 2, "...": "..." }
|
|
214
|
+
],
|
|
215
|
+
[
|
|
216
|
+
{ "id": "fix-avatar-upload-crash", "...": "..." },
|
|
217
|
+
{ "id": "fix-expired-session-redirect", "...": "..." }
|
|
218
|
+
]
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Limitations
|
|
226
|
+
|
|
227
|
+
- View column `sorters` accept the same fields as `kanbn sort` and `columnSorting`, plus `column`. See [sortable fields](filtering-and-sorting.md#sortable-fields).
|
|
228
|
+
- Views only affect `kanbn board`. `kanbn find`, `kanbn status`, `kanbn burndown`, `kanbn gantt` and `kanbn history` don't take a `--view` option.
|
|
229
|
+
- Views are read-only: `kanbn move` and `kanbn add` still work in terms of index columns, not view columns.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Kanbn Examples
|
|
2
|
+
|
|
3
|
+
Each subdirectory is a self-contained Kanbn workspace. `cd` into one and run any Kanbn command
|
|
4
|
+
against it — nothing here affects the boards in the other directories.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
cd example/views
|
|
8
|
+
kanbn board -v by-assignee
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The index and config files in these examples are commented so they can be read as documentation.
|
|
12
|
+
Kanbn rewrites those files whenever it saves (`kanbn add`, `kanbn move`, `kanbn sort --save`,
|
|
13
|
+
`kanbn validate --save`, ...), which reformats the YAML and strips the comments — so run mutating
|
|
14
|
+
commands on a copy if you want to keep them.
|
|
15
|
+
|
|
16
|
+
## [`basic`](basic)
|
|
17
|
+
|
|
18
|
+
A typical in-flight product board. Demonstrates the core setup you get from `kanbn init`:
|
|
19
|
+
|
|
20
|
+
- columns, with `startedColumns` and `completedColumns`
|
|
21
|
+
- tags and `taskWorkloadTags` for workload calculation
|
|
22
|
+
- `sprints`
|
|
23
|
+
- tasks with sub-tasks, `assigned`, `progress`, `due` and `## History` sections
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
kanbn board
|
|
27
|
+
kanbn status --due
|
|
28
|
+
kanbn burndown -p "Workflow Sprint"
|
|
29
|
+
kanbn gantt
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## [`views`](views)
|
|
33
|
+
|
|
34
|
+
Custom board layouts. See [Views](../docs/views.md).
|
|
35
|
+
|
|
36
|
+
- `default` — the index columns, spelled out explicitly
|
|
37
|
+
- `by-assignee` — the same columns, split into one lane per assignee plus an "Unassigned" lane
|
|
38
|
+
- `triage` — a root filter narrowing the whole view to bugs, plus per-column filters and sorters
|
|
39
|
+
- `workload` — columns as workload buckets and lanes as started/not-started
|
|
40
|
+
- `everything` — a single column showing every task, including the hidden `Icebox` column
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
kanbn board
|
|
44
|
+
kanbn board -v by-assignee
|
|
45
|
+
kanbn board -v triage
|
|
46
|
+
kanbn board -v workload
|
|
47
|
+
kanbn board -v everything
|
|
48
|
+
kanbn board -v triage --json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## [`boards`](boards)
|
|
52
|
+
|
|
53
|
+
Several boards over one shared pool of tasks. See [Multiple Boards](../docs/multiple-boards.md).
|
|
54
|
+
|
|
55
|
+
- an engineering board in [`index.md`](boards/.kanbn/index.md) and a design board in
|
|
56
|
+
[`design.md`](boards/.kanbn/design.md), sharing one `tasks/` folder
|
|
57
|
+
- three tasks that sit in **different columns on each board** - including one that design has started
|
|
58
|
+
and engineering hasn't
|
|
59
|
+
- `startedField` / `completedField` on the design board, so design progress is tracked in its own
|
|
60
|
+
metadata fields rather than in the shared `started` / `completed` dates
|
|
61
|
+
- board-scoped options in each board's front matter, and workspace-scoped `customFields` in the main
|
|
62
|
+
board's - where they are inherited by both
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
kanbn boards
|
|
66
|
+
kanbn boards --tasks
|
|
67
|
+
kanbn board -b design
|
|
68
|
+
kanbn status -b design --untracked
|
|
69
|
+
kanbn task build-tenant-settings-page
|
|
70
|
+
kanbn find --all-boards
|
|
71
|
+
kanbn validate --all-boards
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## [`advanced`](advanced)
|
|
75
|
+
|
|
76
|
+
Project configuration beyond the defaults. See
|
|
77
|
+
[Advanced Configuration](../docs/advanced-configuration.md).
|
|
78
|
+
|
|
79
|
+
- all project options live in [`kanbn.yml`](advanced/kanbn.yml) instead of the index front matter
|
|
80
|
+
- `customFields` (`team`, `storyPoints`, `needsDesign`, `reviewedAt`) that can be set, searched and sorted
|
|
81
|
+
- `reviewedAtColumns`, which stamps `reviewedAt` the first time a task enters the `Review` column
|
|
82
|
+
- `columnSorting`, which keeps `Backlog` sorted by workload in the index file itself
|
|
83
|
+
- a custom `taskTemplate` and `dateFormat`
|
|
84
|
+
- a hidden `Blocked` column
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
kanbn board
|
|
88
|
+
kanbn find --team Platform
|
|
89
|
+
kanbn find --storyPoints 3 --storyPoints 8
|
|
90
|
+
kanbn find --needsDesign
|
|
91
|
+
kanbn find --no-needsDesign
|
|
92
|
+
kanbn sort "Backlog" --workload -z --name -a --save
|
|
93
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Advanced Example
|
|
2
|
+
|
|
3
|
+
Project options for this board live in `kanbn.yml` in the workspace root rather
|
|
4
|
+
than in this file's front matter. See `docs/advanced-configuration.md`.
|
|
5
|
+
|
|
6
|
+
## Backlog
|
|
7
|
+
|
|
8
|
+
- [add-sso-support](tasks/add-sso-support.md)
|
|
9
|
+
- [refresh-marketing-site](tasks/refresh-marketing-site.md)
|
|
10
|
+
- [rotate-api-keys](tasks/rotate-api-keys.md)
|
|
11
|
+
|
|
12
|
+
## In Progress
|
|
13
|
+
|
|
14
|
+
- [split-billing-service](tasks/split-billing-service.md)
|
|
15
|
+
|
|
16
|
+
## Review
|
|
17
|
+
|
|
18
|
+
- [add-rate-limiting](tasks/add-rate-limiting.md)
|
|
19
|
+
|
|
20
|
+
## Done
|
|
21
|
+
|
|
22
|
+
- [upgrade-node-runtime](tasks/upgrade-node-runtime.md)
|
|
23
|
+
|
|
24
|
+
## Blocked
|
|
25
|
+
|
|
26
|
+
- [migrate-to-new-cdn](tasks/migrate-to-new-cdn.md)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
created: 2026-06-25T09:00:00.000Z
|
|
3
|
+
updated: 2026-07-18T09:00:00.000Z
|
|
4
|
+
started: 2026-07-08T09:00:00.000Z
|
|
5
|
+
reviewedAt: 2026-07-18T09:00:00.000Z
|
|
6
|
+
assigned: Ana
|
|
7
|
+
progress: 0.9
|
|
8
|
+
due: 2026-07-20T17:00:00.000Z
|
|
9
|
+
team: Platform
|
|
10
|
+
storyPoints: 3
|
|
11
|
+
needsDesign: false
|
|
12
|
+
tags:
|
|
13
|
+
- Medium
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Add Rate Limiting
|
|
17
|
+
|
|
18
|
+
Per-tenant rate limits on the public API.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
created: 2026-07-01T09:00:00.000Z
|
|
3
|
+
updated: 2026-07-01T09:00:00.000Z
|
|
4
|
+
assigned: Ana
|
|
5
|
+
due: 2026-11-01T17:00:00.000Z
|
|
6
|
+
team: Identity
|
|
7
|
+
storyPoints: 13
|
|
8
|
+
needsDesign: true
|
|
9
|
+
tags:
|
|
10
|
+
- Huge
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Add SSO Support
|
|
14
|
+
|
|
15
|
+
Support SAML and OIDC single sign-on for enterprise customers.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
created: 2026-06-10T09:00:00.000Z
|
|
3
|
+
updated: 2026-07-01T09:00:00.000Z
|
|
4
|
+
due: 2026-12-01T17:00:00.000Z
|
|
5
|
+
team: Platform
|
|
6
|
+
storyPoints: 5
|
|
7
|
+
needsDesign: false
|
|
8
|
+
tags:
|
|
9
|
+
- Large
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Migrate To New CDN
|
|
13
|
+
|
|
14
|
+
Blocked until the contract with the new CDN provider is signed.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
created: 2026-07-02T09:00:00.000Z
|
|
3
|
+
updated: 2026-07-02T09:00:00.000Z
|
|
4
|
+
due: 2026-09-15T17:00:00.000Z
|
|
5
|
+
team: Growth
|
|
6
|
+
storyPoints: 5
|
|
7
|
+
needsDesign: true
|
|
8
|
+
tags:
|
|
9
|
+
- Medium
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Refresh Marketing Site
|
|
13
|
+
|
|
14
|
+
Rebuild the landing pages on the new design system.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
created: 2026-07-03T09:00:00.000Z
|
|
3
|
+
updated: 2026-07-03T09:00:00.000Z
|
|
4
|
+
assigned: Ben
|
|
5
|
+
due: 2026-08-01T17:00:00.000Z
|
|
6
|
+
team: Platform
|
|
7
|
+
storyPoints: 2
|
|
8
|
+
needsDesign: false
|
|
9
|
+
tags:
|
|
10
|
+
- Small
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Rotate API Keys
|
|
14
|
+
|
|
15
|
+
Quarterly rotation of all outbound integration keys.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
created: 2026-06-20T09:00:00.000Z
|
|
3
|
+
updated: 2026-07-10T09:00:00.000Z
|
|
4
|
+
started: 2026-07-05T09:00:00.000Z
|
|
5
|
+
assigned: Ben
|
|
6
|
+
progress: 0.4
|
|
7
|
+
due: 2026-10-01T17:00:00.000Z
|
|
8
|
+
team: Platform
|
|
9
|
+
storyPoints: 8
|
|
10
|
+
needsDesign: false
|
|
11
|
+
tags:
|
|
12
|
+
- Large
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Split Billing Service
|
|
16
|
+
|
|
17
|
+
Extract billing out of the monolith into its own service.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
created: 2026-06-01T09:00:00.000Z
|
|
3
|
+
updated: 2026-06-18T09:00:00.000Z
|
|
4
|
+
started: 2026-06-05T09:00:00.000Z
|
|
5
|
+
completed: 2026-06-18T09:00:00.000Z
|
|
6
|
+
reviewedAt: 2026-06-15T09:00:00.000Z
|
|
7
|
+
assigned: Ben
|
|
8
|
+
progress: 1
|
|
9
|
+
due: 2026-06-20T17:00:00.000Z
|
|
10
|
+
team: Platform
|
|
11
|
+
storyPoints: 3
|
|
12
|
+
needsDesign: false
|
|
13
|
+
tags:
|
|
14
|
+
- Medium
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Upgrade Node Runtime
|
|
18
|
+
|
|
19
|
+
Move all services onto the current Node LTS release.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# All project options for this board live in this file instead of the index front matter.
|
|
2
|
+
# Kanbn looks for `kanbn.json` or `kanbn.yml` in the root of the workspace.
|
|
3
|
+
#
|
|
4
|
+
# Note: Kanbn rewrites this file whenever it saves project options (for example
|
|
5
|
+
# `kanbn sort --save`), which strips these comments.
|
|
6
|
+
|
|
7
|
+
startedColumns:
|
|
8
|
+
- 'In Progress'
|
|
9
|
+
- Review
|
|
10
|
+
completedColumns:
|
|
11
|
+
- Done
|
|
12
|
+
|
|
13
|
+
# The Blocked column is hidden from the default board, but still tracked.
|
|
14
|
+
# Any view that defines its own columns can still show it.
|
|
15
|
+
hiddenColumns:
|
|
16
|
+
- Blocked
|
|
17
|
+
|
|
18
|
+
defaultTaskWorkload: 2
|
|
19
|
+
taskWorkloadTags:
|
|
20
|
+
Tiny: 1
|
|
21
|
+
Small: 2
|
|
22
|
+
Medium: 3
|
|
23
|
+
Large: 5
|
|
24
|
+
Huge: 8
|
|
25
|
+
|
|
26
|
+
# Extra metadata fields. Once declared here they can be set with `kanbn add`
|
|
27
|
+
# and `kanbn edit`, searched with `kanbn find`, sorted with `kanbn sort`,
|
|
28
|
+
# filtered in views, and interpolated into taskTemplate.
|
|
29
|
+
customFields:
|
|
30
|
+
- name: team
|
|
31
|
+
type: string
|
|
32
|
+
- name: storyPoints
|
|
33
|
+
type: number
|
|
34
|
+
- name: needsDesign
|
|
35
|
+
type: boolean
|
|
36
|
+
- name: reviewedAt
|
|
37
|
+
type: date
|
|
38
|
+
updateDate: once
|
|
39
|
+
|
|
40
|
+
# `reviewedAt` is stamped the first time a task enters the Review column
|
|
41
|
+
# (`updateDate: once`). Use `always` to re-stamp on every move.
|
|
42
|
+
reviewedAtColumns:
|
|
43
|
+
- Review
|
|
44
|
+
|
|
45
|
+
# The Backlog column is kept sorted in the index file itself, so the order
|
|
46
|
+
# survives `kanbn add`, `kanbn move` and `kanbn validate --save`.
|
|
47
|
+
columnSorting:
|
|
48
|
+
Backlog:
|
|
49
|
+
- field: workload
|
|
50
|
+
order: descending
|
|
51
|
+
- field: name
|
|
52
|
+
order: ascending
|
|
53
|
+
|
|
54
|
+
dateFormat: yyyy-mm-dd
|
|
55
|
+
|
|
56
|
+
# Custom fields (team, storyPoints, ...) can be interpolated here because they
|
|
57
|
+
# are declared in customFields above. See docs/index-structure.md for the full
|
|
58
|
+
# list of available variables.
|
|
59
|
+
taskTemplate: "^+^_${overdue ? '^R' : ''}${name}^:${assigned ? ('\\n^-@' + assigned) : ''}${team ? ('\\n^-' + team) : ''}${storyPoints ? ('\\n^-' + storyPoints + 'pts') : ''}${due ? ('\\n^-due ' + due) : ''}"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Board-scoped: this board has its own workflow, and its own idea of started and completed
|
|
3
|
+
startedColumns:
|
|
4
|
+
- Designing
|
|
5
|
+
completedColumns:
|
|
6
|
+
- 'Signed Off'
|
|
7
|
+
|
|
8
|
+
# ...and its own state fields, so design progress is tracked separately from engineering progress on
|
|
9
|
+
# the same shared task files. Without these, moving a task to "Signed Off" here would set the
|
|
10
|
+
# shared `completed` date and mark it done on the engineering board too
|
|
11
|
+
startedField: designStartedAt
|
|
12
|
+
completedField: designSignedOffAt
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Design Pipeline
|
|
16
|
+
|
|
17
|
+
Design work across the product, tracked separately from engineering delivery. Tasks here are the
|
|
18
|
+
same task files the engineering board uses - a task can sit in a different column on each.
|
|
19
|
+
|
|
20
|
+
## Ideas
|
|
21
|
+
|
|
22
|
+
- [design-onboarding-checklist](tasks/design-onboarding-checklist.md)
|
|
23
|
+
|
|
24
|
+
## Designing
|
|
25
|
+
|
|
26
|
+
- [build-tenant-settings-page](tasks/build-tenant-settings-page.md)
|
|
27
|
+
- [refresh-marketing-site](tasks/refresh-marketing-site.md)
|
|
28
|
+
|
|
29
|
+
## Signed Off
|
|
30
|
+
|
|
31
|
+
- [create-organization-switcher](tasks/create-organization-switcher.md)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Board-scoped: these belong to the main board alone, and are NOT inherited by design.md
|
|
3
|
+
startedColumns:
|
|
4
|
+
- 'In Progress'
|
|
5
|
+
completedColumns:
|
|
6
|
+
- Done
|
|
7
|
+
|
|
8
|
+
# Workspace-scoped: with no config file these live here, and every board inherits them. A custom
|
|
9
|
+
# field has to be declared once for the whole workspace, because one task file has to parse
|
|
10
|
+
# identically for every board that references it
|
|
11
|
+
customFields:
|
|
12
|
+
- name: designStartedAt
|
|
13
|
+
type: date
|
|
14
|
+
- name: designSignedOffAt
|
|
15
|
+
type: date
|
|
16
|
+
taskWorkloadTags:
|
|
17
|
+
Tiny: 1
|
|
18
|
+
Small: 2
|
|
19
|
+
Medium: 3
|
|
20
|
+
Large: 5
|
|
21
|
+
Huge: 8
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Example SaaS Project
|
|
25
|
+
|
|
26
|
+
The engineering board. Design work on the same tasks is tracked separately on the `design` board -
|
|
27
|
+
run `kanbn boards` to see both, and `kanbn boards --tasks` to see which tasks span them.
|
|
28
|
+
|
|
29
|
+
## Backlog
|
|
30
|
+
|
|
31
|
+
- [refresh-marketing-site](tasks/refresh-marketing-site.md)
|
|
32
|
+
- [add-usage-alert-emails](tasks/add-usage-alert-emails.md)
|
|
33
|
+
|
|
34
|
+
## Todo
|
|
35
|
+
|
|
36
|
+
- [create-organization-switcher](tasks/create-organization-switcher.md)
|
|
37
|
+
|
|
38
|
+
## In Progress
|
|
39
|
+
|
|
40
|
+
- [build-tenant-settings-page](tasks/build-tenant-settings-page.md)
|
|
41
|
+
|
|
42
|
+
## Done
|
|
43
|
+
|
|
44
|
+
- [ship-billing-portal](tasks/ship-billing-portal.md)
|