@lovek4d/kickflip 0.6.0 → 0.7.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/CONTEXT.md +42 -26
- package/SKILL.md +16 -12
- package/bin/doctor.mjs +5 -4
- package/bin/event-lines.mjs +1 -0
- package/bin/install.mjs +3 -3
- package/bin/kickflip.mjs +7 -7
- package/bin/wait-events.mjs +2 -2
- package/board/agents.mjs +2 -2
- package/board/arch-review.mjs +1 -1
- package/board/bin/board.mjs +70 -42
- package/board/domain.mjs +7 -7
- package/board/finish.mjs +4 -4
- package/board/headless.mjs +8 -9
- package/board/onboarding.mjs +1 -1
- package/board/prototype-page.mjs +1 -1
- package/board/public/app-arch.js +2 -2
- package/board/public/app-banner.js +1 -1
- package/board/public/app-cards.js +36 -48
- package/board/public/app-lanes.js +6 -9
- package/board/public/app-needs.js +107 -26
- package/board/public/app-reader.js +52 -42
- package/board/public/app-threads.js +14 -10
- package/board/public/app.html +1 -1
- package/board/public/app.js +22 -12
- package/board/public/progress-review.js +4 -4
- package/board/public/prototype-kit.js +25 -11
- package/board/public/prototype-switcher.js +53 -33
- package/board/public/prototype.html +7 -4
- package/board/public/router.js +34 -6
- package/board/public/shell.js +23 -16
- package/board/public/sidebar.css +7 -2
- package/board/public/sidebar.js +3 -0
- package/board/public/style.css +69 -30
- package/board/public/ui-cards.js +61 -38
- package/board/public/ui-enums.js +12 -3
- package/board/public/ui-header.js +4 -6
- package/board/public/ui-hub.js +7 -4
- package/board/public/ui-links.js +1 -1
- package/board/public/ui-order.js +9 -6
- package/board/public/ui-page-header.js +1 -1
- package/board/public/ui-progress-bar.js +6 -7
- package/board/public/ui-questions.js +18 -3
- package/board/public/ui-sidebar.js +12 -5
- package/board/public/ui-snapshot.js +1 -1
- package/board/public/ui-threads.js +10 -12
- package/board/public/ui.js +7 -4
- package/board/public/view-hub.js +3 -1
- package/board/public/view-notes.js +1 -1
- package/board/public/view-questions.js +19 -3
- package/board/public/view-settings.js +60 -28
- package/board/server.mjs +19 -19
- package/board/stop.mjs +1 -1
- package/board/store.mjs +27 -25
- package/board/tickets.mjs +1 -1
- package/board/transcript.mjs +4 -2
- package/hooks-plugin/hooks/board-data-gate.mjs +9 -6
- package/hub/front-door.mjs +1 -1
- package/hub/home.mjs +1 -1
- package/hub/hub.mjs +6 -6
- package/hub/sessions.mjs +2 -2
- package/hub/try.mjs +7 -7
- package/hub/upgrade.mjs +1 -1
- package/hub/usage.mjs +3 -3
- package/package.json +1 -1
- package/shared/card-prs.mjs +1 -1
- package/shared/machine-gate.mjs +2 -2
- package/shared/tailscale.mjs +1 -1
- package/shared/usage-gate.mjs +1 -1
package/board/public/ui.js
CHANGED
|
@@ -60,9 +60,10 @@
|
|
|
60
60
|
// A composer's send buttons: the one with nth 0 presses on ⌘↵ and the one with nth 1, where there is one, on ⌥↵,
|
|
61
61
|
// wherever each sits in the row (Ctrl+↵ and
|
|
62
62
|
// Alt+↵ off a Mac), each with its key as a small gray hint that style.css shows only where there is a keyboard and room.
|
|
63
|
+
// tone is its class: primary for the nth 0 one unless given, as resolves on the Send or Reply that clears Action needed.
|
|
63
64
|
const MAC = /Mac|iPhone|iPad/.test(globalThis.navigator?.platform || globalThis.navigator?.userAgent || '');
|
|
64
65
|
const SEND_KEYS = MAC ? ['⌘↵', '⌥↵'] : ['Ctrl+↵', 'Alt+↵'];
|
|
65
|
-
const sendButton = (label, nth, attrs = '') => `<button type="submit"${
|
|
66
|
+
const sendButton = (label, nth, attrs = '', tone = nth === 0 ? 'primary' : '') => `<button type="submit"${tone ? ` class="${tone}"` : ''} data-nth="${nth}"${attrs}>${esc(label)}<span class="key-hint" aria-hidden="true">${SEND_KEYS[nth]}</span></button>`;
|
|
66
67
|
// The send button an Enter keydown in form's box presses, or null.
|
|
67
68
|
const keyedSend = (ev, form) => {
|
|
68
69
|
if (ev.key !== 'Enter' || ev.isComposing) return null;
|
|
@@ -102,6 +103,8 @@
|
|
|
102
103
|
'ask-link': mini('<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>'),
|
|
103
104
|
move: mini('<path d="M3 7h14"/><path d="m13 3 4 4-4 4"/><path d="M21 17H7"/><path d="m11 13-4 4 4 4"/>'),
|
|
104
105
|
'Implementation requested': mini('<path d="m8 6-6 6 6 6"/><path d="m16 6 6 6-6 6"/>'),
|
|
106
|
+
'Marked as staging': mini('<path d="m12 2 10 5-10 5L2 7z"/><path d="m2 12 10 5 10-5"/><path d="m2 17 10 5 10-5"/>'),
|
|
107
|
+
'Marked as ready': mini('<path d="M4 22V4"/><path d="M4 4h13l-2 4 2 4H4"/>'),
|
|
105
108
|
Held: mini('<path d="M9 5v14"/><path d="M15 5v14"/>'),
|
|
106
109
|
Resumed: mini('<path d="M7 4v16l13-8z"/>'),
|
|
107
110
|
'Related to': LINK, 'Unrelated from': LINK, 'Follow-up': LINK, 'Follow-up of': LINK,
|
|
@@ -121,8 +124,8 @@
|
|
|
121
124
|
// Deletes or edits a pending ask or thread message; app-asks.js handles [data-withdraw] and [data-edit] on one path.
|
|
122
125
|
const withdrawButton = (id) => `<button class="ghost icon" data-withdraw="${esc(id)}" aria-label="Delete" title="Delete">${TRASH}</button>`;
|
|
123
126
|
const editButton = (id) => `<button class="ghost icon" data-edit="${esc(id)}" aria-label="Edit" title="Edit">${PENCIL}</button>`;
|
|
124
|
-
// A waiting ask's bump, which app-asks.js sends as ask-bump {id}, filled orange
|
|
125
|
-
//
|
|
127
|
+
// A waiting ask's bump, which app-asks.js sends as ask-bump {id}, filled orange and disabled once
|
|
128
|
+
// bumped. A work card's is the same icon: app-cards.js sends card-bump {key} with no confirm, and on a bumped card, where
|
|
126
129
|
// it is filled orange, card-unbump {key} (docs/adr/2026-10-29-1639-a-card-that-hasnt-started-can-be-bumped.md,
|
|
127
130
|
// docs/adr/2026-11-03-0301-build-on-a-blocked-card-queues-it.md).
|
|
128
131
|
const bumpButton = (it, top) => `<button class="ghost icon${it.bumpedAt ? ' bumped' : ''}" data-bump="${esc(it.id)}" aria-label="Bump to top" title="Bump to top"${top ? ' disabled' : ''}>${BUMP}</button>`;
|
|
@@ -379,7 +382,7 @@
|
|
|
379
382
|
removed: { line: 'This board is gone', hint: 'The hub no longer lists it.', retries: false },
|
|
380
383
|
access: { line: 'You no longer have access', hint: 'Ask its owner to share it again.', retries: false },
|
|
381
384
|
erroring: { line: 'Board restarting', hint: 'It answers with errors. Retrying for 30 seconds.' },
|
|
382
|
-
unreachable: { line: 'Board offline', hint: 'The hub, your connection or the
|
|
385
|
+
unreachable: { line: 'Board offline', hint: 'The hub, your connection or the host is down. Retrying.' },
|
|
383
386
|
};
|
|
384
387
|
const RESTART_WHY = { code: 'Its code changed.', starting: 'It’s starting.', crashed: 'It stopped, and the hub is starting it again.' };
|
|
385
388
|
function downOf(answer, online = navigator.onLine !== false) {
|
package/board/public/view-hub.js
CHANGED
|
@@ -184,7 +184,9 @@ ${integrationsHtml(d.integrations)}${sitesHtml(d.groups, restarting)}${d.remembe
|
|
|
184
184
|
answer(out, action) {
|
|
185
185
|
if (['/_hub/settings/codex', '/_hub/settings/claude-model', '/_hub/settings/pause-at', '/_hub/settings/keep-going'].includes(action)) {
|
|
186
186
|
last = out; flash = null; paint();
|
|
187
|
-
if (action.endsWith('/keep-going')) B.toast(out.pause.state === 'going' ? `Keep going: work runs past the buffer until ${BoardUI.usageClock(out.pause.resets_at)}.`
|
|
187
|
+
if (action.endsWith('/keep-going')) B.toast(out.pause.state === 'going' ? `Keep going: work runs past the buffer until ${BoardUI.usageClock(out.pause.resets_at)}.`
|
|
188
|
+
: out.pause.near?.going ? `Keep going: agents start in parallel until ${BoardUI.usageClock(out.pause.near.resets_at)}.`
|
|
189
|
+
: out.pause.near ? 'Stopped: agents start one at a time again.' : 'Stopped: new work waits again.');
|
|
188
190
|
} else if (action === '/_hub/push/flush') { flash = { text: out.message, error: !out.ok }; load(); } else { flash = null; load(); settings.B.refreshSettings?.(); }
|
|
189
191
|
ctx.sidebarChanged?.();
|
|
190
192
|
},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
(globalThis.AppViews ||= {}).notes = {
|
|
5
5
|
className: 'view-notes',
|
|
6
6
|
title: () => 'Notes',
|
|
7
|
-
markup: ({ item, wave }) => `${BoardUI.header({ lead: 'back', title: 'Notes', titleId: 'ref', backHref: wave ? './' : `./#
|
|
7
|
+
markup: ({ item, wave }) => `${BoardUI.header({ lead: 'back', title: 'Notes', titleId: 'ref', backHref: wave ? './' : `./#selectedCard=${encodeURIComponent(item || '')}`, controls: false })}
|
|
8
8
|
<main>
|
|
9
9
|
<article class="plan" id="plan"><p class="muted">Loading the notes…</p></article>
|
|
10
10
|
</main>`,
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// The question then reads prototype requested, locked, until its prototype round arrives, and once the request is
|
|
12
12
|
// recorded the page moves on as a Send does (docs/adr/2026-11-03-1803-a-question-asked-for-a-prototype-is-resolved.md,
|
|
13
13
|
// docs/adr/2026-11-04-2130-a-prototype-request-waits-for-its-prototype.md).
|
|
14
|
-
// A question the owner has sent an answer to is resolved: read-only,
|
|
14
|
+
// A question the owner has sent an answer to is resolved: read-only, with a Resolved badge. One that resolves
|
|
15
15
|
// while its topic shows stays in place until you leave that topic, by a pill, a link or the move on after the last
|
|
16
16
|
// Send; a topic's sent questions otherwise fold under "resolved (N)", which names only the count, above its open ones. A sent question's badge, once confirmed, sends `question-reopen {key?, question}`, and the question
|
|
17
17
|
// opens again with its last pick and note for a new Send. Each question is numbered by its place on its card
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
return `<div class="topics" role="group" aria-label="Topics">${list.map((t) => pillHtml(t)).join('')}</div>`;
|
|
86
86
|
}
|
|
87
87
|
// An arrival (arrive) slides in with a glow and settles (style.css .arrived).
|
|
88
|
-
const pillHtml = (t, arrived = false) => `<button class="${arrived ? 'arrived ' : ''}${t.progress ? 'progress' : ''}" data-topic="${esc(t.id)}" aria-pressed="${t.id === topicId}" title="${esc(t.title)}">${esc(topicName(t))}
|
|
88
|
+
const pillHtml = (t, arrived = false) => `<button class="${arrived ? 'arrived ' : ''}${t.progress ? 'progress' : ''}" data-topic="${esc(t.id)}" aria-pressed="${t.id === topicId}" title="${esc(t.title)}"><span class="name">${esc(topicName(t))}</span><span class="n">${t.state === 'resolved' ? 'resolved' : t.open}</span></button>`;
|
|
89
89
|
const topicHead = (t) => t ? `<div class="topichead muted small"><span>${esc(t.title)}${t.summary ? ` · ${esc(t.summary)}` : ''}</span>${roundChip(t.round)}</div>` : '';
|
|
90
90
|
|
|
91
91
|
// Only open questions changed here since their last answer, with files attached, reopened with a pick, or with a
|
|
@@ -150,10 +150,12 @@
|
|
|
150
150
|
updateBar();
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
// A toast names a topic by its card's ref, the title up to its colon; an ask by its time.
|
|
154
|
+
const toastName = (t) => (t.askedAt ? topicName(t) : String(t.title).match(/^(\S+):/)?.[1] || t.title);
|
|
153
155
|
// The topic a tap on this page acts for, the pills before it and its name, which moveOn reads once it is recorded.
|
|
154
156
|
function leaving() {
|
|
155
157
|
const from = soloId ? topicOf(soloId)?.id : topicId;
|
|
156
|
-
return { from, pills: topicRow(topics, from).pills, name:
|
|
158
|
+
return { from, pills: topicRow(topics, from).pills, name: toastName(topics.find((t) => t.id === from) || { title: '' }) };
|
|
157
159
|
}
|
|
158
160
|
// After a recorded tap: a load that started before it went out would read its question as still open.
|
|
159
161
|
// `footer` is a line or, for a Send, a function of the reloaded board giving the whole line.
|
|
@@ -364,6 +366,14 @@
|
|
|
364
366
|
scroll(0);
|
|
365
367
|
}
|
|
366
368
|
|
|
369
|
+
let lastTap = { id: null, at: 0 };
|
|
370
|
+
// item:<key> is a card, wave:<slug> a band. Embedded, the board is beside the pane; alone, it is the page before this one.
|
|
371
|
+
function openOnBoard(id) {
|
|
372
|
+
if (ctx.embedded) return ctx.openOnBoard(id);
|
|
373
|
+
if (!id.startsWith('item:')) return;
|
|
374
|
+
const href = new URL(`./#item=${encodeURIComponent(id.slice(5))}`, location.href).href;
|
|
375
|
+
if (!AppShell.open(href)) location.assign(href);
|
|
376
|
+
}
|
|
367
377
|
B.on(root, 'click', (ev) => {
|
|
368
378
|
if (ev.target.id === 'showAll') { soloId = null; sentLine = ''; shown = ''; render(); return; }
|
|
369
379
|
const eye = ev.target.closest && ev.target.closest('[data-proto]');
|
|
@@ -372,9 +382,14 @@
|
|
|
372
382
|
if (reopenBadge) { if (!reopenBadge.disabled) reopen(byId(reopenBadge.dataset.reopen), reopenBadge); return; }
|
|
373
383
|
const b = ev.target.closest && ev.target.closest('[data-topic]');
|
|
374
384
|
if (b) {
|
|
385
|
+
// Two taps on one pill within 300ms open its card on the board; a touch screen sends no dblclick.
|
|
386
|
+
const now = Date.now();
|
|
387
|
+
if (lastTap.id === b.dataset.topic && now - lastTap.at < 300) { lastTap = { id: null, at: 0 }; openOnBoard(b.dataset.topic); return; }
|
|
388
|
+
lastTap = { id: b.dataset.topic, at: now };
|
|
375
389
|
soloId = null; setTopic(b.dataset.topic); sentLine = ''; shown = '';
|
|
376
390
|
const first = topics.find((t) => t.id === topicId)?.questions[0];
|
|
377
391
|
if (first) setHash(first);
|
|
392
|
+
ctx.onTopic?.(topicId);
|
|
378
393
|
render(); scroll(0);
|
|
379
394
|
}
|
|
380
395
|
});
|
|
@@ -441,6 +456,7 @@
|
|
|
441
456
|
if (!loaded) return false;
|
|
442
457
|
const topic = topics.find((t) => t.id === id);
|
|
443
458
|
if (topic) { soloId = null; setTopic(topic.id); } else pick(id || '');
|
|
459
|
+
ctx.onTopic?.(topicId);
|
|
444
460
|
sentLine = '';
|
|
445
461
|
shown = '';
|
|
446
462
|
render();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Board Settings, a modal the app shell (shell.js) mounts over its board for /<route>/settings.html.
|
|
2
2
|
// Saving reviewers sends a `reviewers` event {add: [{name, slack_user, github}], remove: [name]}; the server writes both
|
|
3
|
-
// itself and the list redraws from its answer. Add
|
|
4
|
-
//
|
|
3
|
+
// itself and the list redraws from its answer. Add searches the Slack members the server fetches (GET api/reviewer-people), then
|
|
4
|
+
// takes a GitHub login guessed from the name or typed, and stays off until a member and a login are there. Tickets save on each change, and Done, ✕,
|
|
5
5
|
// Escape and the backdrop wait for that save to land before closing. An unsaved edit marks [data-dirty], and the shell
|
|
6
6
|
// asks before closing the modal then. Sharing (docs/adr/2026-11-05-2215-a-board-is-shared-view-only-with-a-tailscale-login.md)
|
|
7
7
|
// lists the hub's GET /_hub/share/<route>, who it's shared with, each View or Full, and who was just refused, and saves
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
<p class="muted small">Everyone here gets each PR link on this board once it's ready, and the weekday digest. Someone you add also gets the links of PRs already in review.</p>
|
|
77
77
|
<div id="list"><p class="muted small">Loading…</p></div>
|
|
78
78
|
<form class="add pick" id="addForm">
|
|
79
|
-
<
|
|
80
|
-
<div
|
|
81
|
-
<button type="submit" id="addBtn" disabled>Add</button>
|
|
79
|
+
<input id="slackFilter" placeholder="Search Slack members" autocomplete="off">
|
|
80
|
+
<div id="slackPeople" class="pick-list"><p class="muted small">Loading…</p></div>
|
|
81
|
+
<div class="login-row" id="loginRow" hidden><input id="githubFilter" placeholder="GitHub login" autocomplete="off" autocapitalize="off" spellcheck="false"><button type="submit" id="addBtn" disabled>Add</button></div>
|
|
82
82
|
</form>
|
|
83
83
|
<p class="muted small" id="pickStatus" role="status"></p>
|
|
84
84
|
<div class="row" style="margin-top:12px"><span class="muted small" id="saveStatus"></span><button class="primary" id="save" disabled>Save</button></div>
|
|
@@ -362,40 +362,72 @@
|
|
|
362
362
|
render();
|
|
363
363
|
});
|
|
364
364
|
|
|
365
|
-
// The
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
// The picker: search the Slack members and tap one, then the GitHub login field holds a guess from the name (or what
|
|
366
|
+
// the person types), and Add stages them for Save. people is null while loading, 'failed' once the list can't be read.
|
|
367
|
+
let people = null;
|
|
368
|
+
const picked = { slack: null };
|
|
368
369
|
const fits = (text, filter) => text.toLowerCase().includes(filter.trim().toLowerCase());
|
|
370
|
+
const squash = (text) => text.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
371
|
+
// The collaborator whose login is a plain spelling of the name: first+last, first, last, or first initial + last.
|
|
372
|
+
function guessLogin(name, logins) {
|
|
373
|
+
const words = name.toLowerCase().split(/\s+/).map(squash).filter(Boolean);
|
|
374
|
+
if (!words.length) return '';
|
|
375
|
+
const first = words[0], last = words.at(-1);
|
|
376
|
+
const spellings = [first + last, first, last, first[0] + last];
|
|
377
|
+
for (const spelling of spellings) {
|
|
378
|
+
const hit = logins.find((l) => squash(l) === spelling);
|
|
379
|
+
if (hit) return hit;
|
|
380
|
+
}
|
|
381
|
+
return '';
|
|
382
|
+
}
|
|
383
|
+
const problem = (text) => `<p class="muted small problem">${esc(text)} <button type="button" class="ghost" data-retry>Retry</button></p>`;
|
|
369
384
|
function renderPick() {
|
|
370
|
-
const slackFilter = $('#slackFilter').value, githubFilter = $('#githubFilter').value.trim();
|
|
371
385
|
const taken = new Set([...current, ...add.keys()].map((n) => n.toLowerCase()));
|
|
372
|
-
const
|
|
373
|
-
$('#
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
386
|
+
const list = $('#slackPeople');
|
|
387
|
+
$('#slackFilter').hidden = Boolean(picked.slack);
|
|
388
|
+
$('#loginRow').hidden = !picked.slack;
|
|
389
|
+
if (picked.slack) {
|
|
390
|
+
list.innerHTML = `<span class="chip">${esc(picked.slack.name)} <button type="button" class="ghost" data-unpick aria-label="Choose someone else">✕</button></span>`;
|
|
391
|
+
$('#pickStatus').textContent = people?.githubProblem ? `${people.githubProblem} Type their login.` : '';
|
|
392
|
+
} else if (people === 'failed') list.innerHTML = problem("Couldn't load the list.");
|
|
393
|
+
else if (!people) list.innerHTML = '<p class="muted small">Loading…</p>';
|
|
394
|
+
else if (!people.slack) list.innerHTML = `<p class="muted small">${esc(people.slackProblem || 'Slack members are not available.')} <a href="/_hub/settings">Hub settings</a></p>`;
|
|
395
|
+
else list.innerHTML = people.slack.filter((p) => fits(p.name, $('#slackFilter').value) && !taken.has(p.name.toLowerCase()))
|
|
396
|
+
.map((p) => `<button type="button" class="ghost" data-slack="${esc(p.id)}" data-name="${esc(p.name)}">${esc(p.name)}</button>`).join('') || '<p class="muted small">No one matches.</p>';
|
|
397
|
+
$('#addBtn').disabled = !picked.slack || !$('#githubFilter').value.trim();
|
|
398
|
+
}
|
|
399
|
+
function loadPeople() {
|
|
400
|
+
people = null;
|
|
401
|
+
renderPick();
|
|
402
|
+
// The wait says "Board restarting…" only when the hub marked a restart; any other wait leaves Loading… until the
|
|
403
|
+
// list arrives or the retries give out.
|
|
404
|
+
const wait = (ms, down) => { if (down?.state === 'restarting') $('#slackPeople').innerHTML = restartNotice(); };
|
|
405
|
+
get(site.url('api/reviewer-people'), wait).then(({ ok, body }) => { if (!B.alive()) return; people = ok ? { github: [], ...body } : 'failed'; renderPick(); })
|
|
406
|
+
.catch(() => { if (B.alive()) { people = 'failed'; renderPick(); } });
|
|
380
407
|
}
|
|
381
|
-
|
|
382
|
-
.catch(() => { if (B.alive()) $('#slackPeople').innerHTML = '<p class="muted small">Offline. Is the host awake?</p>'; });
|
|
408
|
+
loadPeople();
|
|
383
409
|
B.on($('#slackFilter'), 'input', renderPick);
|
|
384
410
|
B.on($('#githubFilter'), 'input', renderPick);
|
|
385
411
|
B.on($('#slackPeople'), 'click', (ev) => {
|
|
386
|
-
const b = ev.target.closest('button
|
|
387
|
-
if (b)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
412
|
+
const b = ev.target.closest('button');
|
|
413
|
+
if (!b) return;
|
|
414
|
+
if (b.dataset.retry !== undefined) { loadPeople(); return; }
|
|
415
|
+
if (b.dataset.unpick !== undefined) { picked.slack = null; $('#githubFilter').value = ''; renderPick(); return; }
|
|
416
|
+
if (!b.dataset.slack) return;
|
|
417
|
+
picked.slack = { id: b.dataset.slack, name: b.dataset.name };
|
|
418
|
+
$('#githubFilter').value = guessLogin(picked.slack.name, people.github);
|
|
419
|
+
renderPick();
|
|
420
|
+
$('#pickStatus').textContent = $('#githubFilter').value ? 'Guessed from their name. Change it if it is wrong.' : $('#pickStatus').textContent;
|
|
421
|
+
$('#githubFilter').focus?.();
|
|
392
422
|
});
|
|
393
423
|
B.on($('#addForm'), 'submit', (ev) => {
|
|
394
424
|
ev.preventDefault();
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
picked.slack
|
|
425
|
+
const github = $('#githubFilter').value.trim();
|
|
426
|
+
if (!picked.slack || !github) return;
|
|
427
|
+
add.set(picked.slack.name, { name: picked.slack.name, slack_user: picked.slack.id, github });
|
|
428
|
+
picked.slack = null;
|
|
398
429
|
$('#slackFilter').value = ''; $('#githubFilter').value = '';
|
|
430
|
+
$('#pickStatus').textContent = '';
|
|
399
431
|
renderPick();
|
|
400
432
|
render();
|
|
401
433
|
});
|