Capture UX: "+ New" header button + type-to-compose (two-mode board keys)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 9s
CI & Build / Build & push image (push) Successful in 30s

Live-pass feedback: the wide "Take a note…" bar showed through behind the
compose modal and felt redundant. Per operator choice, remove the bar and
make capture header-button + keyboard driven.

- Removed the board's inline "Take a note…" trigger bar.
- AppShell gains a "+ New" header button (next to search) — navigates to the
  board if needed, then opens the compose modal. The `c` shortcut now does
  the same (unified with newNote()).
- Type-to-compose: on the board with no card focused, any other single
  printable key opens a new note SEEDED with that key (AppShell onKeydown
  fall-through, after the reserved / c ? g shortcuts). Seed travels via
  ui.composeSeed → NoteEditor's new `initialBody` prop; caret placed at end.
- Two-mode board keyboard (BoardView): RESTING = arrows enter browse, letters
  type-to-compose; BROWSING (a card focused) = j/k move, e/x/# act, Enter
  opens, Esc exits to resting. ui.boardCardFocused tells the global handler
  to stand down while browsing so it doesn't swallow card keys.
- Updated the shortcuts help + the empty-state copy ("Hit + New — or just
  start typing").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
2026-07-23 13:59:23 -04:00
co-authored by Claude Opus 4.8
parent f9f1b77d37
commit 675b3aa248
4 changed files with 83 additions and 33 deletions
+7 -3
View File
@@ -18,8 +18,9 @@ import { LABEL_CHIP_CLASSES, type NoteColor } from "../notes/colors";
// or null to compose a new one. In compose mode there is no note id until a draft is
// persisted — on commit-with-content, or on the first rich action (label / image /
// reminder / checklist) — so we never litter empty notes.
const props = withDefaults(defineProps<{ note?: Note | null }>(), {
const props = withDefaults(defineProps<{ note?: Note | null; initialBody?: string }>(), {
note: null,
initialBody: "",
});
const emit = defineEmits<{ (e: "close"): void; (e: "navigate", id: string): void }>();
const notes = useNotesStore();
@@ -28,7 +29,7 @@ const titles = useTitlesStore();
const noteId = ref<string | null>(props.note?.id ?? null);
const title = ref(props.note?.title ?? "");
const body = ref(props.note?.body ?? "");
const body = ref(props.note?.body ?? props.initialBody);
const color = ref<NoteColor>(props.note?.color ?? "default");
const labelList = ref<NoteLabel[]>(props.note ? [...props.note.labels] : []);
const createKind = ref<"text" | "list">("text"); // compose-only list toggle
@@ -206,7 +207,10 @@ onMounted(async () => {
void titles.load();
void loadBacklinks();
await nextTick();
bodyInput.value?.focus();
const el = bodyInput.value;
el?.focus();
// Put the caret after any seeded text (type-to-compose) so typing continues cleanly.
if (el) el.selectionStart = el.selectionEnd = el.value.length;
});
// ---- outgoing links (edit mode) ----