generated from nhcarrigan/template
452fe185df
## Summary This PR brings Hikari Desktop up to full compatibility with Claude Code CLI versions v2.1.68 through v2.1.74, implementing all changelog items audited in issues #200–#218. ## Changes ### Bug Fixes - Remove deprecated Claude Opus 4.0 and 4.1 models from the model selector - Auto-migrate users pinned to deprecated models to Opus 4.6 ### New Features - Add cron tool support (`CronCreate`, `CronDelete`, `CronList`) with character state mapping and `CLAUDE_CODE_DISABLE_CRON` settings toggle - Handle `EnterWorktree` and `ExitWorktree` tools in character state mapping and tool display - Add CLI update check with npm registry indicator in the version bar - Add `agent_type` field and support the Agent tool rename from CLI v2.1.69 - Consume `worktree` field from status line hook events - Display per-agent model override in the agent monitor tree - Expose Claude Code CLI built-in slash commands (`/simplify`, `/loop`, `/batch`, `/memory`, `/context`) in the command menu with CLI badges - Add `includeGitInstructions` toggle in settings - Add `ENABLE_CLAUDEAI_MCP_SERVERS` opt-out setting - Linkify MCP binary file paths (PDFs, audio, Office docs) in markdown output - Add auto-memory panel, `/memory` slash command shortcut, and unified toast notification system - Toast notifications for `WorktreeCreate` and `WorktreeRemove` hook events - Sort session resume list by most recent activity, with most recent user message as preview - Convert WSL Linux paths to Windows UNC paths when opening binary files via `open_binary_file` command - Expose `autoMemoryDirectory` setting in ConfigSidebar (Agent Settings section) - Add `/context` as a CLI built-in in the slash command menu - Expose `modelOverrides` setting as a JSON textarea in ConfigSidebar (for AWS Bedrock, Google Vertex, etc.) > **Note:** The CLI update check commit does not have a corresponding issue — it was a bonus addition during the audit sprint. ## Closes Closes #200 Closes #201 Closes #202 Closes #205 Closes #206 Closes #207 Closes #208 Closes #209 Closes #210 Closes #211 Closes #212 Closes #213 Closes #214 Closes #215 Closes #216 Closes #217 Closes #218 Reviewed-on: #221 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
200 lines
7.2 KiB
Svelte
200 lines
7.2 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from "svelte";
|
|
import { fade, fly } from "svelte/transition";
|
|
import { cubicOut } from "svelte/easing";
|
|
import { listen } from "@tauri-apps/api/event";
|
|
import { openUrl } from "@tauri-apps/plugin-opener";
|
|
import { toastStore, getAchievementRarity, getRarityColour } from "$lib/stores/toasts";
|
|
import type { AchievementUnlockedEvent } from "$lib/types/achievements";
|
|
|
|
const toasts = toastStore;
|
|
|
|
onMount(() => {
|
|
let unlisten: (() => void) | undefined;
|
|
|
|
const setupListener = async () => {
|
|
unlisten = await listen<AchievementUnlockedEvent>("achievement:unlocked", (event) => {
|
|
toastStore.addAchievement(event.payload.achievement);
|
|
});
|
|
};
|
|
|
|
setupListener();
|
|
|
|
return () => {
|
|
if (unlisten) {
|
|
unlisten();
|
|
}
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<div class="fixed top-20 right-4 z-50 flex flex-col gap-3 items-end">
|
|
{#each $toasts as toast (toast.id)}
|
|
<div in:fly={{ x: 300, duration: 500, easing: cubicOut }} out:fade={{ duration: 300 }}>
|
|
{#if toast.kind === "info"}
|
|
<!-- Info toast -->
|
|
<div
|
|
class="bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg p-3 shadow-lg flex items-center gap-2 max-w-sm"
|
|
>
|
|
<span class="text-xl shrink-0">{toast.icon}</span>
|
|
<span class="text-sm text-[var(--text-primary)] flex-1">{toast.message}</span>
|
|
<button
|
|
onclick={() => toastStore.remove(toast.id)}
|
|
class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors shrink-0"
|
|
aria-label="Dismiss"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
{:else if toast.kind === "achievement"}
|
|
{@const rarity = getAchievementRarity(toast.achievement.id)}
|
|
{@const colour = getRarityColour(rarity)}
|
|
<!-- Achievement toast -->
|
|
<div class="relative p-[2px] rounded-lg overflow-hidden max-w-sm">
|
|
<!-- Animated gradient border -->
|
|
<div class="absolute inset-0 bg-gradient-to-r {colour} animate-pulse"></div>
|
|
|
|
<!-- Main content -->
|
|
<div class="relative bg-[var(--bg-primary)] rounded-lg p-4 shadow-2xl backdrop-blur-sm">
|
|
<button
|
|
onclick={() => toastStore.remove(toast.id)}
|
|
class="absolute top-2 right-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
|
|
aria-label="Dismiss notification"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
|
|
<div class="flex items-start gap-4">
|
|
<!-- Icon with animated sparkles -->
|
|
<div class="relative flex-shrink-0">
|
|
<div class="text-5xl animate-bounce">{toast.achievement.icon}</div>
|
|
<div class="absolute -top-1 -right-1 text-yellow-400 animate-ping">✨</div>
|
|
<div
|
|
class="absolute -bottom-1 -left-1 text-yellow-400 animate-ping animation-delay-200"
|
|
>
|
|
✨
|
|
</div>
|
|
<div
|
|
class="absolute top-1/2 -right-2 text-yellow-400 animate-ping animation-delay-400"
|
|
>
|
|
✨
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Text content -->
|
|
<div class="flex-1 min-w-0 pt-1">
|
|
<h3
|
|
class="text-sm font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide"
|
|
>
|
|
Achievement Unlocked!
|
|
</h3>
|
|
<p class="text-lg font-bold text-[var(--text-primary)] mt-1">
|
|
{toast.achievement.name}
|
|
</p>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
{toast.achievement.description}
|
|
</p>
|
|
|
|
<!-- Rarity badge -->
|
|
<div class="mt-2 inline-flex items-center">
|
|
<span
|
|
class="px-2 py-1 text-xs font-medium rounded-full bg-gradient-to-r {colour} text-white capitalize"
|
|
>
|
|
{rarity}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Confetti particles -->
|
|
<div class="absolute inset-0 pointer-events-none overflow-hidden rounded-lg">
|
|
{#each Array.from({ length: 10 }, (_, i) => i) as confettiIndex (confettiIndex)}
|
|
<div
|
|
class="absolute w-2 h-2 bg-gradient-to-br {colour} rounded-full animate-fall"
|
|
style="left: {(confettiIndex * 11) % 100}%; animation-delay: {(confettiIndex *
|
|
0.3) %
|
|
2}s; animation-duration: {2 + ((confettiIndex * 0.25) % 2)}s;"
|
|
></div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{:else if toast.kind === "update"}
|
|
<!-- Update toast -->
|
|
<div
|
|
class="bg-[var(--bg-tertiary)] border border-[var(--accent-primary)] rounded-lg p-4 shadow-lg max-w-sm"
|
|
>
|
|
<div class="flex items-start gap-3">
|
|
<div class="text-2xl">🎉</div>
|
|
<div class="flex-1">
|
|
<h3 class="text-[var(--text-primary)] font-semibold mb-1">Update Available!</h3>
|
|
<button
|
|
onclick={() => openUrl(toast.releaseUrl)}
|
|
class="text-[var(--accent-primary)] font-mono hover:underline text-sm"
|
|
>
|
|
{toast.latestVersion}
|
|
</button>
|
|
<p class="text-[var(--text-muted)] text-xs mt-1">
|
|
Current version: {toast.currentVersion}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onclick={() => toastStore.remove(toast.id)}
|
|
class="text-[var(--text-muted)] hover:text-[var(--text-primary)] transition-colors shrink-0"
|
|
aria-label="Dismiss"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
|
|
<style>
|
|
@keyframes fall {
|
|
0% {
|
|
transform: translateY(-20px) rotate(0deg);
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
transform: translateY(400px) rotate(720deg);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.animate-fall {
|
|
animation: fall linear infinite;
|
|
}
|
|
|
|
.animation-delay-200 {
|
|
animation-delay: 200ms;
|
|
}
|
|
|
|
.animation-delay-400 {
|
|
animation-delay: 400ms;
|
|
}
|
|
</style>
|