desktop: a global hotkey opens a small window to write in, now with its lockfile
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 13s
CI & Build / integration (push) Successful in 29s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 1m5s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 1m52s
Desktop (Tauri) / Update manifest (push) Skipped
Android / Kotlin + Rust (APK) (push) Successful in 7m22s

Restores 42e06da, which was reverted only because Cargo.lock had not been
updated for the new crate and every cargo invocation in CI passes `--locked`.
Both desktop jobs failed on that line before compiling anything, so nothing
about the code had been judged.

The lockfile was generated in CI's own `ci-tauri:1.97` image — one container,
`cargo fetch`, nothing built. `cargo fetch` and NOT `generate-lockfile`: the
latter re-resolves from scratch and would have churned versions across the
whole workspace to add one dependency. The diff is 67 insertions, zero
deletions, six packages — tauri-plugin-global-shortcut plus global-hotkey,
x11rb, x11rb-protocol, xkeysym and gethostname. Nothing existing moved.

The feature itself, unchanged from 42e06da:

Press the combination anywhere and a small window arrives over whatever you
were doing; type, Ctrl/Cmd+Enter, gone. The board never comes forward.

There is no default shortcut on purpose — any default is a key combination
taken away from something else on somebody's machine, silently, at install
time. CommandOrControl+Shift+N is offered as a one-click suggestion.

Stored and live are separate fields because they disagree: a combination
another app holds is saved and does nothing when pressed, and a Wayland
compositor may refuse global grabs outright. `capture_shortcut_set` registers
before storing, so a refused combination is never written down as if it worked.

The window hides rather than closes and keeps its text, so an interrupted
capture is still there next press — which is what makes Escape safe. A failed
save keeps it open too, rather than discarding the only copy of something just
written in order to report a retryable problem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K3MMqUtzX1TJgA1oypvm1c
This commit is contained in:
2026-09-01 18:05:20 -04:00
co-authored by Claude Opus 5
parent 10ea15bef0
commit 6c0153be1e
10 changed files with 578 additions and 3 deletions
+87
View File
@@ -0,0 +1,87 @@
<script setup lang="ts">
// The quick-capture window: one field, and two ways out.
//
// This runs in a SECOND Tauri window, summoned by a global hotkey over whatever
// the person was doing. Everything here is shaped by that: no shell, no nav, no
// board — a window that arrives uninvited has to be finishable in one gesture and
// leave nothing behind if it isn't.
import { nextTick, onMounted, ref } from "vue";
import { repo } from "../adapters";
import { capture } from "../desktop/bridge";
const body = ref("");
const field = ref<HTMLTextAreaElement | null>(null);
const saving = ref(false);
const error = ref("");
onMounted(async () => {
// Focused on arrival, and after a save. The whole feature is "press the keys and
// start typing" — a window that needs a click first has not saved anyone a step.
await nextTick();
field.value?.focus();
});
async function save() {
const content = body.value.trim();
// Nothing typed is not an error, it is a change of mind — the same reading the
// board takes of tapping + and walking away.
if (!content) {
void capture.done(false);
return;
}
saving.value = true;
error.value = "";
try {
await repo.notes.create({ body: content });
body.value = "";
await capture.done(true);
} catch {
// The window STAYS OPEN on failure, holding the text. Hiding it would throw
// away the only copy of something the person just wrote, to report a problem
// they could otherwise retry their way out of.
error.value = "Couldn't save that. Your text is still here — try again.";
} finally {
saving.value = false;
}
}
function dismiss() {
// The text is deliberately KEPT. The window is hidden rather than destroyed, so
// a capture interrupted by something more urgent is still there on the next
// press — which is the behaviour that makes it safe to press Escape.
void capture.done(false);
}
</script>
<template>
<div
class="flex h-screen w-screen flex-col gap-2 bg-neutral-50 p-3 text-neutral-900 dark:bg-neutral-950 dark:text-neutral-100"
>
<textarea
ref="field"
v-model="body"
class="min-h-0 flex-1 resize-none rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-900"
placeholder="Write it down…"
aria-label="New note"
@keydown.esc.prevent="dismiss"
@keydown.enter.ctrl.prevent="save"
@keydown.enter.meta.prevent="save"
/>
<p v-if="error" class="text-xs text-red-600 dark:text-red-400">{{ error }}</p>
<div class="flex items-center justify-between gap-3">
<!-- The shortcuts are written down rather than assumed: this window is seen
rarely and briefly, and it is the only place they are discoverable. -->
<p class="text-xs text-neutral-400">
<kbd>Ctrl</kbd>/<kbd></kbd> + <kbd>Enter</kbd> to save · <kbd>Esc</kbd> to dismiss
</p>
<div class="flex shrink-0 items-center gap-2">
<button type="button" class="btn btn-ghost" @click="dismiss">Cancel</button>
<button type="button" class="btn btn-primary" :disabled="saving" @click="save">
{{ saving ? "Saving" : "Save" }}
</button>
</div>
</div>
</div>
</template>