@goodandready/dsh-kanban 0.1.20 → 0.1.21

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 CHANGED
@@ -127,7 +127,25 @@ A due date can be set on any card. Overdue cards are highlighted, and a switch i
127
127
  the board header narrows the board to them alone — the answer to "what is already
128
128
  late" is one click, not a scan.
129
129
 
130
- ### 6. Board Snapshot: Export and Import
130
+ ### 6. Ownership: Who Took the Task
131
+
132
+ The board carries two different people per task and never confuses them: the
133
+ **author** who filed the issue and the **assignee** who took it. Both arrive
134
+ from Gitea, both are filter dimensions, and the assignee is shown on the card
135
+ itself — "who is doing this" is asked more often than "who wrote it".
136
+
137
+ A single button in the task window takes the task or drops it. Taking assigns
138
+ the account whose token the board uses: the harness has no user of its own, so
139
+ "me" is resolved by asking Gitea rather than by guessing. The change travels
140
+ back to the issue through the outbound queue, so the board keeps working while
141
+ Gitea is down; dropping sends an empty assignee list, because Gitea reads a
142
+ missing field as "leave it alone".
143
+
144
+ Assignment made in Gitea arrives on the next sync — in both directions,
145
+ including removal. Filtering by "nobody" answers the question the board is
146
+ opened for most often: what is free to pick up.
147
+
148
+ ### 7. Board Snapshot: Export and Import
131
149
 
132
150
  `GET /dsh-kanban/snapshot` returns the whole board as JSON; `POST` to the same
133
151
  route restores it. Import is idempotent: a task whose id is already on the board
package/lib/client.js CHANGED
@@ -682,6 +682,7 @@ window.__ModuleLoader__.load({
682
682
  '.dkb-archiveRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;align-items:center;gap:12px;padding:8px 12px;display:flex}' +
683
683
  '.dkb-start{display:contents}' +
684
684
  '.dkb-pack{color:var(--dsw-alias-label-secondary)}' +
685
+ '.dkb-who{color:var(--dsw-alias-label-secondary)}' +
685
686
  '.dkb-sameSession{outline:1px solid var(--dsw-alias-label-secondary);outline-offset:1px}' +
686
687
  '.dkb-waitCount{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:0 10px;height:30px;cursor:pointer;color:var(--dsw-alias-label-primary)}' +
687
688
  '.dkb-archiveOpen{background:none;border:0;padding:0;text-align:left;cursor:pointer;font:inherit}' +
@@ -946,6 +947,12 @@ window.__ModuleLoader__.load({
946
947
  'sync.neverSeen': 'no successful check since the harness started',
947
948
  'facet.repo': 'Repository',
948
949
  'facet.author': 'Author',
950
+ 'facet.assignee': 'Assignee',
951
+ 'card.assigneeHint': 'Who took the task',
952
+ 'panel.assignee': 'taken by {who}',
953
+ 'panel.assignMe': 'Take it',
954
+ 'panel.assignDrop': 'Drop it',
955
+ 'error.gitea-unreachable': 'Gitea did not answer, so there is nobody to take the task as.',
949
956
  'board.pickArchive': 'To the archive',
950
957
  'board.pickUnqueue': 'Out of the queue',
951
958
  'board.pickMove': 'Move to…',
@@ -1191,6 +1198,12 @@ window.__ModuleLoader__.load({
1191
1198
  'sync.neverSeen': '自 harness 启动以来没有一次成功的核对',
1192
1199
  'facet.repo': '仓库',
1193
1200
  'facet.author': '创建者',
1201
+ 'facet.assignee': '负责人',
1202
+ 'card.assigneeHint': '谁接下了这个任务',
1203
+ 'panel.assignee': '由 {who} 接手',
1204
+ 'panel.assignMe': '我来接',
1205
+ 'panel.assignDrop': '放手',
1206
+ 'error.gitea-unreachable': 'Gitea 没有响应,无从得知以谁的身份接手。',
1194
1207
  'board.pickArchive': '移入归档',
1195
1208
  'board.pickUnqueue': '移出队列',
1196
1209
  'board.pickMove': '移动到…',
@@ -1623,6 +1636,10 @@ window.__ModuleLoader__.load({
1623
1636
  title: exactAt(task.dueAt),
1624
1637
  }, t('card.due', { date: shortDate(task.dueAt) }))
1625
1638
  : null,
1639
+ task.assignee
1640
+ ? React.createElement('span', { className: 'dkb-who', title: t('card.assigneeHint') },
1641
+ '@' + task.assignee)
1642
+ : null,
1626
1643
  pack
1627
1644
  ? React.createElement('span', {
1628
1645
  className: 'dkb-pack', title: t('card.packHint'),
@@ -2355,6 +2372,7 @@ window.__ModuleLoader__.load({
2355
2372
  React.createElement('div', { className: 'dkb-title' }, openTask.title),
2356
2373
  React.createElement('div', { className: 'dkb-note' },
2357
2374
  t('panel.created', { ago: agoText(state.now, openTask.createdAt, t) })
2375
+ + (openTask.assignee ? ' · ' + t('panel.assignee', { who: openTask.assignee }) : '')
2358
2376
  // Автор стоит рядом с датой заведения: это одно событие — кто и
2359
2377
  // когда завёл, — и разносить его по разным углам незачем.
2360
2378
  + (openTask.author ? ' · ' + t('panel.author', { who: openTask.author }) : ''),
@@ -2420,6 +2438,22 @@ window.__ModuleLoader__.load({
2420
2438
  },
2421
2439
  }, t('panel.saveEdit')),
2422
2440
  ),
2441
+ // Взять и отдать — одна кнопка в двух состояниях: держать рядом
2442
+ // «взять» и «отказаться» значит предлагать бессмысленное действие.
2443
+ React.createElement('button', {
2444
+ type: 'button', className: 'dkb-discard', style: { alignSelf: 'flex-start' },
2445
+ onClick: async () => {
2446
+ const mine = openTask.assignee !== '' && openTask.assignee !== undefined
2447
+ try {
2448
+ const out = await api('/task/' + encodeURIComponent(openTask.id) + '/assign', {
2449
+ method: 'POST',
2450
+ body: JSON.stringify({ login: mine ? '' : 'me' }),
2451
+ })
2452
+ if (out && out.task) setOpenTask(out.task)
2453
+ await load(board)
2454
+ } catch (e) { setError(t(e.key || 'error.unknown')) }
2455
+ },
2456
+ }, openTask.assignee ? t('panel.assignDrop') : t('panel.assignMe')),
2423
2457
  openTask.sessionId
2424
2458
  ? React.createElement('button', {
2425
2459
  type: 'button', className: 'dkb-discard', style: { alignSelf: 'flex-start' },