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
Restores42e06da, 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 from42e06da: 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
88 lines
3.2 KiB
Vue
88 lines
3.2 KiB
Vue
<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>
|