feat: another wave of features (#61)
CI / Build Linux (push) Has been cancelled
CI / Build Windows (cross-compile) (push) Has been cancelled
Security Scan and Upload / Security & DefectDojo Upload (push) Has been cancelled
CI / Lint & Test (push) Has been cancelled

## Explanation

This PR bundles several user-facing improvements and feature additions for the v0.3.0 release, including quality-of-life improvements to the UI, new slash commands, better state persistence, and auto-update checking.

## Included Changes

- **Resizable chat input** with drag handle (#58 partial)
- **Arrow key navigation fix** - cursor keys now navigate text when user has typed input (#58)
- **Scroll position persistence** per conversation tab
- **/skill command** for invoking Claude Code skills (#57)
- **Stats persistence fix** - stats now persist across session changes, only reset on disconnect (#59)
- **Auto-update checker** on startup (#17)
- **Resizable character panel** with full-height sprites (#10)
- **Font size and zoom settings** with keyboard shortcuts (Ctrl++/Ctrl+-/Ctrl+0) (#19)

## Closes

Closes #10, #17, #19, #57, #58, #59

## Attestations

- [x] I have read and agree to the Code of Conduct
- [x] I have read and agree to the Community Guidelines
- [x] My contribution complies with the Contributor Covenant
- [x] I have run the linter and resolved any errors
- [x] My pull request uses an appropriate title, matching the conventional commit standards
- [x] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request
- [x] All new and existing tests pass locally with my changes
- [x] Code coverage remains at or above the configured threshold

## Documentation

N/A - Internal app features

## Versioning

Minor - My pull request introduces new non-breaking features.

---
 This PR was created with help from Hikari~ 🌸

Co-authored-by: Hikari <hikari@nhcarrigan.com>
Reviewed-on: #61
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit was merged in pull request #61.
This commit is contained in:
2026-01-23 19:07:22 -08:00
committed by Naomi Carrigan
parent 06810537a9
commit 3f30997f0e
34 changed files with 1562 additions and 306 deletions
+2
View File
@@ -64,6 +64,8 @@ export const claudeStore = {
deleteConversation: conversationsStore.deleteConversation,
switchConversation: conversationsStore.switchConversation,
renameConversation: conversationsStore.renameConversation,
saveScrollPosition: conversationsStore.saveScrollPosition,
getScrollPosition: conversationsStore.getScrollPosition,
getGrantedTools: (): string[] => {
let tools: string[] = [];
+50
View File
@@ -15,6 +15,9 @@ export interface HikariConfig {
notifications_enabled: boolean;
notification_volume: number;
always_on_top: boolean;
update_checks_enabled: boolean;
character_panel_width: number | null;
font_size: number;
}
const defaultConfig: HikariConfig = {
@@ -29,6 +32,9 @@ const defaultConfig: HikariConfig = {
notifications_enabled: true,
notification_volume: 0.7,
always_on_top: false,
update_checks_enabled: true,
character_panel_width: null,
font_size: 14,
};
function createConfigStore() {
@@ -89,6 +95,33 @@ function createConfigStore() {
applyTheme(theme);
},
setFontSize: async (size: number) => {
const clampedSize = Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, size));
await updateConfig({ font_size: clampedSize });
applyFontSize(clampedSize);
},
increaseFontSize: async () => {
let currentConfig: HikariConfig = defaultConfig;
config.subscribe((c) => (currentConfig = c))();
const newSize = Math.min(MAX_FONT_SIZE, currentConfig.font_size + 2);
await updateConfig({ font_size: newSize });
applyFontSize(newSize);
},
decreaseFontSize: async () => {
let currentConfig: HikariConfig = defaultConfig;
config.subscribe((c) => (currentConfig = c))();
const newSize = Math.max(MIN_FONT_SIZE, currentConfig.font_size - 2);
await updateConfig({ font_size: newSize });
applyFontSize(newSize);
},
resetFontSize: async () => {
await updateConfig({ font_size: DEFAULT_FONT_SIZE });
applyFontSize(DEFAULT_FONT_SIZE);
},
addAutoGrantedTool: async (tool: string) => {
let currentConfig: HikariConfig = defaultConfig;
config.subscribe((c) => (currentConfig = c))();
@@ -119,6 +152,23 @@ export function applyTheme(theme: Theme) {
}
}
const MIN_FONT_SIZE = 10;
const MAX_FONT_SIZE = 24;
const DEFAULT_FONT_SIZE = 14;
export function applyFontSize(size: number) {
if (typeof document !== "undefined") {
const clampedSize = Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, size));
document.documentElement.style.setProperty("--terminal-font-size", `${clampedSize}px`);
}
}
export function clampFontSize(size: number): number {
return Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, size));
}
export { MIN_FONT_SIZE, MAX_FONT_SIZE, DEFAULT_FONT_SIZE };
export const configStore = createConfigStore();
export const isDarkTheme = derived(configStore.config, ($config) => $config.theme === "dark");
+20
View File
@@ -21,6 +21,7 @@ export interface Conversation {
grantedTools: Set<string>;
pendingPermission: PermissionRequest | null;
pendingQuestion: UserQuestionEvent | null;
scrollPosition: number;
createdAt: Date;
lastActivityAt: Date;
}
@@ -55,6 +56,7 @@ function createConversationsStore() {
grantedTools: new Set(),
pendingPermission: null,
pendingQuestion: null,
scrollPosition: -1, // -1 means "scroll to bottom" (auto-scroll)
createdAt: new Date(),
lastActivityAt: new Date(),
};
@@ -106,6 +108,7 @@ function createConversationsStore() {
($conv) => $conv?.pendingPermission || null
);
const pendingQuestion = derived(activeConversation, ($conv) => $conv?.pendingQuestion || null);
const scrollPosition = derived(activeConversation, ($conv) => $conv?.scrollPosition ?? -1);
return {
// Expose derived stores for compatibility
@@ -118,6 +121,7 @@ function createConversationsStore() {
isProcessing: { subscribe: isProcessing.subscribe },
grantedTools: { subscribe: grantedTools.subscribe },
pendingRetryMessage: { subscribe: pendingRetryMessage.subscribe },
scrollPosition: { subscribe: scrollPosition.subscribe },
// New conversation-specific stores
conversations: { subscribe: conversations.subscribe },
@@ -325,6 +329,22 @@ function createConversationsStore() {
});
},
saveScrollPosition: (id: string, position: number) => {
conversations.update((convs) => {
const conv = convs.get(id);
if (conv) {
conv.scrollPosition = position;
}
return convs;
});
},
getScrollPosition: (id: string): number => {
const convs = get(conversations);
const conv = convs.get(id);
return conv?.scrollPosition ?? -1;
},
// Methods that operate on the active conversation
setSessionId: (id: string | null) => {
ensureInitialized();