Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- **Cmd+N shortcut for Add Sibling Note** — Remapped Cmd+N from New Workspace to Add Sibling Note (far more frequent action). Cmd+Shift+N remains Add Child Note. New Workspace stays in File menu without a shortcut (PR #198).
- **AddNoteDialog auto-focus and type pre-selection** — Type dropdown is auto-focused on open so arrow keys work immediately. Adding a sibling pre-selects the reference note's schema type for rapid same-type note creation (PR #198).
- **Auto-focus tree on workspace open** — Tree panel receives focus after initial note selection so keyboard navigation (arrow keys, Enter to edit) works immediately without a mouse click (PR #198).

### Fixed
- **Edit focus falls through when title is hidden** — When a schema hides the title field, edit mode now focuses the first schema field instead of leaving no focus (PR #198).
- **Tag input no longer traps Tab/Enter when empty** — Tab and Enter pass through to normal keyboard navigation and quick-save when the tag input is empty, instead of being swallowed by the tag autocomplete handler (PR #198).
- **Relay polling resilience** — Parse `Retry-After` header from relay 429 responses into structured `RelayRateLimited` error with `retry_after_secs`. Downgrade attachment blob-skip log from warn to debug for deleted notes (expected during normal sync of append-only op log).

## [1.1.0] — 2026-05-09

### Security
Expand Down
560 changes: 280 additions & 280 deletions krillnotes-desktop/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion krillnotes-desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ const MENU_MESSAGES: &[(&str, &str)] = &[
("file_open", "File > Open Workspace clicked"),
("file_export", "File > Export Workspace clicked"),
("file_import", "File > Import Workspace clicked"),
("edit_add_note", "Edit > Add Note clicked"),
("edit_add_note", "Edit > Add Child Note clicked"),
("edit_add_sibling", "Edit > Add Sibling Note clicked"),
("edit_delete_note", "Edit > Delete Note clicked"),
("view_refresh", "View > Refresh clicked"),
("help_about", "Help > About Krillnotes clicked"),
Expand Down
13 changes: 10 additions & 3 deletions krillnotes-desktop/src-tauri/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ fn build_file_menu<R: Runtime>(
) -> Result<FileMenuResult<R>, tauri::Error> {
let new_item =
MenuItemBuilder::with_id("file_new", s(strings, "newWorkspace", "New Workspace"))
.accelerator("CmdOrCtrl+N")
.build(app)?;
let open_item = MenuItemBuilder::with_id(
"file_open",
Expand Down Expand Up @@ -262,10 +261,17 @@ fn build_edit_menu<R: Runtime>(
app: &AppHandle<R>,
strings: &Value,
) -> Result<EditMenuResult<R>, tauri::Error> {
let add_note = MenuItemBuilder::with_id("edit_add_note", s(strings, "addNote", "Add Note"))
let add_note = MenuItemBuilder::with_id("edit_add_note", s(strings, "addNote", "Add Child Note"))
.accelerator("CmdOrCtrl+Shift+N")
.enabled(false)
.build(app)?;
let add_sibling = MenuItemBuilder::with_id(
"edit_add_sibling",
s(strings, "addSiblingNote", "Add Sibling Note"),
)
.accelerator("CmdOrCtrl+N")
.enabled(false)
.build(app)?;
let delete_note =
MenuItemBuilder::with_id("edit_delete_note", s(strings, "deleteNote", "Delete Note"))
.accelerator("CmdOrCtrl+Backspace")
Expand Down Expand Up @@ -297,6 +303,7 @@ fn build_edit_menu<R: Runtime>(

let builder = SubmenuBuilder::new(app, s(strings, "edit", "Edit")).items(&[
&add_note,
&add_sibling,
&delete_note,
&sep1,
&copy_note,
Expand All @@ -322,7 +329,7 @@ fn build_edit_menu<R: Runtime>(
submenu,
paste_as_child: paste_child,
paste_as_sibling: paste_sibling,
workspace_items: vec![add_note, delete_note, copy_note],
workspace_items: vec![add_note, add_sibling, delete_note, copy_note],
})
}

Expand Down
16 changes: 11 additions & 5 deletions krillnotes-desktop/src/components/AddNoteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//
// Copyright (c) 2024-2026 TripleACS Pty Ltd t/a 2pi Software

import { useState, useEffect, useMemo } from 'react';
import { useState, useEffect, useMemo, useRef } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { useTranslation } from 'react-i18next';
import type { Note, SchemaInfo } from '../types';
Expand All @@ -25,22 +25,27 @@ function AddNoteDialog({ isOpen, onClose, onNoteCreated, referenceNoteId, positi
const [noteSchema, setNoteSchema] = useState<string>('');
const [error, setError] = useState<string>('');
const [loading, setLoading] = useState(false);
const selectRef = useRef<HTMLSelectElement>(null);

const availableTypes = useMemo(
() => getAvailableTypes(position, referenceNoteId, notes, schemas),
[position, referenceNoteId, notes, schemas]
);

useEffect(() => {
if (availableTypes.length > 0 && !availableTypes.includes(noteSchema)) {
setNoteSchema(availableTypes[0]);
}
}, [availableTypes, noteSchema]);
if (availableTypes.length === 0) return;
const refNote = referenceNoteId ? notes.find(n => n.id === referenceNoteId) : null;
const preferred = position === 'sibling' && refNote && availableTypes.includes(refNote.schema)
? refNote.schema
: availableTypes[0];
setNoteSchema(preferred);
}, [availableTypes, referenceNoteId, position, notes]);

useEffect(() => {
if (isOpen) {
setError('');
setLoading(false);
requestAnimationFrame(() => selectRef.current?.focus());
}
}, [isOpen]);

Expand Down Expand Up @@ -80,6 +85,7 @@ function AddNoteDialog({ isOpen, onClose, onNoteCreated, referenceNoteId, positi
<p className="text-sm text-amber-600 py-2">{t('notes.noAllowedTypes')}</p>
) : (
<select
ref={selectRef}
value={noteSchema}
onChange={(e) => setNoteSchema(e.target.value)}
className="w-full bg-secondary border border-secondary rounded px-3 py-2"
Expand Down
4 changes: 3 additions & 1 deletion krillnotes-desktop/src/components/InfoPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ function InfoPanel({ selectedNote, onNoteUpdated, onDeleteRequest, requestEditMo
{ activeTab, setActiveTab, previousTab, setPreviousTab, setViewHtml },
onNoteUpdated, onEditDone,
isEditing, setIsEditing,
panelRef,
);

// Wire the stable ref so handleSchemaLoaded can call setEditedFields from the hook
Expand Down Expand Up @@ -465,7 +466,8 @@ function InfoPanel({ selectedNote, onNoteUpdated, onDeleteRequest, requestEditMo
autoCapitalize="off"
spellCheck={false}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === 'Tab') {
const hasContent = tagInput.trim() || tagSuggestions.length > 0;
if ((e.key === 'Enter' || e.key === 'Tab') && hasContent) {
e.preventDefault();
if (tagSuggestions.length > 0) addTag(tagSuggestions[0]);
else if (tagInput.trim()) addTag(tagInput);
Expand Down
8 changes: 7 additions & 1 deletion krillnotes-desktop/src/components/WorkspaceView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,12 @@ function WorkspaceView({ workspaceInfo, onOpenWorkspacePeers, sharingIndicatorMo
// Set up menu listener
useEffect(() => {
const unlisten = getCurrentWebviewWindow().listen<string>('menu-action', (event) => {
if (event.payload === 'Edit > Add Note clicked') {
if (event.payload === 'Edit > Add Child Note clicked') {
openAddDialogRef.current?.('child', selectedNoteIdRef.current);
}
if (event.payload === 'Edit > Add Sibling Note clicked') {
openAddDialogRef.current?.('sibling', selectedNoteIdRef.current);
}
if (event.payload === 'Edit > Manage Scripts clicked') {
setShowScriptManager(true);
}
Expand Down Expand Up @@ -261,6 +264,9 @@ function WorkspaceView({ workspaceInfo, onOpenWorkspacePeers, sharingIndicatorMo
setSelectedNoteId(firstRootId);
await invoke('set_selected_note', { noteId: firstRootId });
}
requestAnimationFrame(() => {
treePanelRef.current?.querySelector<HTMLElement>('[tabindex="0"]')?.focus();
});
}

return fetchedNotes;
Expand Down
12 changes: 10 additions & 2 deletions krillnotes-desktop/src/hooks/useNoteForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function useNoteForm(
// useSchema needs isEditing as a parameter, and useNoteForm needs schemaInfo from useSchema.
isEditingExternal: boolean,
setIsEditingExternal: React.Dispatch<React.SetStateAction<boolean>>,
panelRef: React.RefObject<HTMLDivElement | null>,
) {
const { t } = useTranslation();
const { activeTab, setActiveTab, previousTab, setPreviousTab, setViewHtml } = schemaCallbacks;
Expand Down Expand Up @@ -66,11 +67,18 @@ export function useNoteForm(
}
}, [selectedNote?.id]);

// Effect 7: Auto-focus title input when edit mode activates
// Effect 7: Auto-focus first editable field when edit mode activates.
// Prefers title input; falls back to the first input/select/textarea in the panel.
useEffect(() => {
if (!isEditing) return;
const rafId = requestAnimationFrame(() => {
titleInputRef.current?.focus();
if (titleInputRef.current) {
titleInputRef.current.focus();
} else {
panelRef.current?.querySelector<HTMLElement>(
'input:not([type="checkbox"]):not(.kn-tag-editor__input), textarea, select'
)?.focus();
}
});
return () => cancelAnimationFrame(rafId);
}, [isEditing]);
Expand Down
3 changes: 2 additions & 1 deletion krillnotes-desktop/src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@
"manageIdentities": "Identitäten verwalten…",
"exportWorkspace": "Arbeitsbereich exportieren…",
"importWorkspace": "Arbeitsbereich importieren…",
"addNote": "Notiz hinzufügen",
"addNote": "Unternotiz hinzufügen",
"addSiblingNote": "Geschwisternotiz hinzufügen",
"deleteNote": "Notiz löschen",
"copyNote": "Notiz kopieren",
"pasteAsChild": "Als Kind einfügen",
Expand Down
3 changes: 2 additions & 1 deletion krillnotes-desktop/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@
"manageIdentities": "Manage Identities…",
"exportWorkspace": "Export Workspace…",
"importWorkspace": "Import Workspace…",
"addNote": "Add Note",
"addNote": "Add Child Note",
"addSiblingNote": "Add Sibling Note",
"deleteNote": "Delete Note",
"copyNote": "Copy Note",
"pasteAsChild": "Paste as Child",
Expand Down
3 changes: 2 additions & 1 deletion krillnotes-desktop/src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@
"manageIdentities": "Gestionar identidades…",
"exportWorkspace": "Exportar espacio de trabajo…",
"importWorkspace": "Importar espacio de trabajo…",
"addNote": "Añadir nota",
"addNote": "Añadir nota hija",
"addSiblingNote": "Añadir nota hermana",
"deleteNote": "Eliminar nota",
"copyNote": "Copiar nota",
"pasteAsChild": "Pegar como hijo",
Expand Down
3 changes: 2 additions & 1 deletion krillnotes-desktop/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@
"manageIdentities": "Gérer les identités…",
"exportWorkspace": "Exporter l'espace de travail…",
"importWorkspace": "Importer un espace de travail…",
"addNote": "Ajouter une note",
"addNote": "Ajouter une note enfant",
"addSiblingNote": "Ajouter une note sœur",
"deleteNote": "Supprimer la note",
"copyNote": "Copier la note",
"pasteAsChild": "Coller comme enfant",
Expand Down
3 changes: 2 additions & 1 deletion krillnotes-desktop/src/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@
"manageIdentities": "IDを管理…",
"exportWorkspace": "ワークスペースを書き出す…",
"importWorkspace": "ワークスペースを読み込む…",
"addNote": "ノートを追加",
"addNote": "子ノートを追加",
"addSiblingNote": "兄弟ノートを追加",
"deleteNote": "ノートを削除",
"copyNote": "ノートをコピー",
"pasteAsChild": "子として貼り付け",
Expand Down
3 changes: 2 additions & 1 deletion krillnotes-desktop/src/i18n/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@
"manageIdentities": "신원 관리…",
"exportWorkspace": "작업 공간 내보내기…",
"importWorkspace": "작업 공간 가져오기…",
"addNote": "노트 추가",
"addNote": "하위 노트 추가",
"addSiblingNote": "형제 노트 추가",
"deleteNote": "노트 삭제",
"copyNote": "노트 복사",
"pasteAsChild": "자식으로 붙여넣기",
Expand Down
3 changes: 2 additions & 1 deletion krillnotes-desktop/src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@
"manageIdentities": "管理身份…",
"exportWorkspace": "导出工作区…",
"importWorkspace": "导入工作区…",
"addNote": "添加笔记",
"addNote": "添加子笔记",
"addSiblingNote": "添加同级笔记",
"deleteNote": "删除笔记",
"copyNote": "复制笔记",
"pasteAsChild": "粘贴为子节点",
Expand Down
Loading