hotkeys, gitea ticket-from-card, collab unread, runner auto-retry
board hotkeys (public/js/app.js):
* "c" toggles the calendar from the board (no modifier, body focus,
no full-screen overlay in front).
* "t" opens a tag quick-pick popover that lists every unique tag from
state.cards. Filter input + arrow-key/enter pick + ESC/click-outside
close. Picking sets state.activeTags = [tag] and re-renders the
board + tag bar. Toggle: a second "t" press closes the picker.
* "1"-"9" picks the Nth user bucket in the bar. The fixed filters
(all/current/favorites/journal/none) are skipped. Super-buckets
count as one slot and activate via the new activateBucket() helper
in buckets.js — that bypasses the click handler so the dropdown
popover does NOT open; the currently shown member (parent or
active child) is selected directly.
* ESC chain in the existing handler now also closes #calendar (was
missing — calendar.js only handles ESC for the day-popup, not the
whole overlay).
buckets (public/js/modules/buckets.js):
* New exported activateBucket(idOrSpecial) helper: setActiveBucket
+ clear activeGroup + renderBar + window._renderBoard. Used by
the number-key hotkey to select super-buckets without triggering
their dropdown.
board split panel (public/js/modules/board.js):
* closeBoardPreview() now removes the .active class from the
toolbar's #board-split-toggle. The previous code removed
.text-t-accent which was a no-op since toggleBoardSplit() uses
.active — the X button on the panel closed the split but left
the toolbar showing the toggle as still on.
email plugin (public/plugins/email/email.js):
* ESC closes the overlay reliably: the previous handler was on
#email-overlay, which only fires when focus is *inside* the
overlay (rare after open). Moved to a document-level keydown.
* Bare "m" toggles the mail overlay open/closed. Suppressed when
Ctrl/Meta/Alt is held, focus is in an input/textarea/contenteditable,
or any other full-screen overlay is in front.
collab-buckets (public/plugins/collab-buckets/backend.php):
* When a card change is applied in the pull phase from a foreign
author (insert / update / tombstone), bump
collab_buckets.updated_at to NOW for that bucket. has_unread is
computed as updated_at > last_seen_at and was previously never
triggered for incoming cards — the bucket-bar tab now gets the
unread dot + 3-pulse when the user is looking at a different
bucket. Own pushes are skipped (the relay echoes them back too).
gitea plugin: make ticket from card (public/plugins/gitea/gitea.js):
* New window._giteaMakeIssueFromCard(card) helper. Opens the gitea
overlay, switches to the create-issue form, fetches the card body
if it's lazy/null, and pre-fills #gitea-create-title and
#gitea-create-body. Does NOT submit — the user picks the repo and
reviews before clicking submit. Soft-bails on the welcome view
if no gitea account is configured yet.
context-menu (public/js/modules/context-menu.js):
* New "Make ticket" entry in the card right-click menu, only rendered
when window._giteaMakeIssueFromCard is exposed. Action handler
hides the menu and calls the helper. Sits next to "Copy text".
* joinSelectedCards() now dedupes hashtag mentions in the merged body
(first occurrence wins, case-insensitive) so a tag that appeared on
multiple source cards isn't repeated in the resulting body. Followed
by a tab/space/blank-line cleanup pass.
test runner auto-retry (public/plugins/tests/runner.js):
* runAll() takes an optional Set<string> of `${suite}::${test}` keys
and runs only those when set. Progress bar denominator reflects
the filtered count.
* run() collects every failed test after the first pass, restores the
backup so the retry runs against the same baseline state, then
re-runs only those via runAll(failedKeys). Retry results are merged
into the original results array with `retried: true` and the
original error preserved on `originalError`.
* renderResults distinguishes three states: passed first try (green ✓),
pass-on-retry (amber ⚠ "flaky — passed on retry" with the first-run
error shown), failed twice (red ✗ "failed twice"). Summary line
carries a "N flaky" count and goes amber if only flaky tests
appeared in the run.
tests (public/plugins/tests/context-menu.test.js):
* Two new tests for the make-ticket action:
- action exists in the menu when window._giteaMakeIssueFromCard
is exposed (soft-skip otherwise)
- clicking it opens the gitea overlay, surfaces the create-issue
form, and pre-fills title + body without submitting. Soft-skips
the assertion when no gitea account is configured (welcome view).
chore: bump version to 0.9.7
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
285e3b4abe
commit
923de0cdd6
10 files changed
+436
-38
No files matched your search
@@ -506,6 +506,13 @@ function collabSyncCards(PDO $db): array {
|
||||
// the delete back to the relay we snapshot the highest tombstone id
|
||||
// before apply and purge everything > that marker afterwards.
|
||||
$tombstoneMark = (int)$db->query('SELECT COALESCE(MAX(id), 0) FROM collab_tombstones')->fetchColumn();
|
||||
// Buckets that received changes from a foreign author. Used at the end
|
||||
// of the apply phase to bump collab_buckets.updated_at, which the
|
||||
// frontend's has_unread calculation reads — that's how a different
|
||||
// bucket-bar tab gets the unread dot + 3-pulse when a peer pushes a
|
||||
// card while the user is looking at something else.
|
||||
$selfUser = collabRelaySettings()['user'];
|
||||
$unreadBuckets = [];
|
||||
try {
|
||||
foreach ($incoming as $c) {
|
||||
// Resolve local bucket id
|
||||
@@ -514,6 +521,8 @@ function collabSyncCards(PDO $db): array {
|
||||
$localBucketId = $bid->fetchColumn();
|
||||
if (!$localBucketId) continue; // bucket mirror missing — shouldn't happen
|
||||
|
||||
$foreignAuthor = ($c['author_username'] ?? '') !== $selfUser;
|
||||
|
||||
// Find local card by relay_uid — grab all fields so we can
|
||||
// stash a conflict copy if needed before overwriting
|
||||
$find = $db->prepare('SELECT id, title, body, color, tags, position, due_date, updated_at FROM cards WHERE relay_uid = :u');
|
||||
@@ -525,6 +534,7 @@ function collabSyncCards(PDO $db): array {
|
||||
$db->prepare('DELETE FROM cards WHERE id = :id')->execute(['id' => $local['id']]);
|
||||
$db->prepare('DELETE FROM collab_card_state WHERE card_id = :id')->execute(['id' => $local['id']]);
|
||||
$stats['deleted_local']++;
|
||||
if ($foreignAuthor) $unreadBuckets[$c['bucket_uid']] = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -609,8 +619,21 @@ function collabSyncCards(PDO $db): array {
|
||||
'r' => $c['updated_at'],
|
||||
]);
|
||||
$stats['pulled']++;
|
||||
if ($foreignAuthor) $unreadBuckets[$c['bucket_uid']] = true;
|
||||
}
|
||||
} finally {
|
||||
// Bump `collab_buckets.updated_at` for every bucket that received
|
||||
// changes from another user. The frontend's has_unread is
|
||||
// `updated_at > last_seen_at`, so this is what triggers the unread
|
||||
// dot + 3-pulse on the bucket-bar tab when the user is looking at
|
||||
// something else. Skipped for the user's own pushes since the
|
||||
// relay echoes those back too.
|
||||
if ($unreadBuckets) {
|
||||
$bumpStmt = $db->prepare("UPDATE collab_buckets SET updated_at = datetime('now') WHERE uid = :u");
|
||||
foreach (array_keys($unreadBuckets) as $u) {
|
||||
$bumpStmt->execute(['u' => $u]);
|
||||
}
|
||||
}
|
||||
// Purge any tombstones the apply phase itself created. Tombstones
|
||||
// queued before apply (by actual user deletes) survive and get
|
||||
// drained on the next poll iteration.
|
||||
|
||||
Reference in new issue
Block a user