generated from nhcarrigan/template
e6e9f7ae59
## Summary A large productivity-focused feature branch delivering a suite of improvements across automation, project management, theming, performance, and documentation. ### Features - **Guided Project Workflow** (#189) — Four-phase workflow panel (Discuss → Plan → Execute → Verify) to keep projects structured from idea to completion - **Automated Task Loop** (#179) — Per-task conversation orchestration with wave-based parallel execution, blocked-task detection, and concurrency control - **Wave-Based Parallel Execution** (#191) — Tasks run in dependency-aware waves with configurable concurrency; independent tasks execute in parallel - **Auto-Commit After Task Completion** (#192) — Task Loop optionally commits after each completed task so progress is never lost - **PRD Creator** (#180) — AI-assisted PRD and task list panel that outputs `hikari-tasks.json` for the Task Loop to consume - **Project Context Panel** (#188) — Persistent `PROJECT.md`, `REQUIREMENTS.md`, `ROADMAP.md`, and `STATE.md` files injected into Claude's context automatically - **Codebase Mapper** (#190) — Generates a `CODEBASE.md` architectural summary so Claude always understands the project structure - **Community Preset Themes** (#181) — Six built-in community themes: Dracula, Catppuccin Mocha, Nord, Solarized Dark, Gruvbox Dark, and Rosé Pine - **In-App Changelog Panel** (#193) — Fetches release notes from GitHub at runtime and displays them inside the app - **Full Embedded Documentation** (#196) — Replaced the single-page help modal with a 12-page paginated docs browser featuring a sidebar TOC, prev/next navigation, keyboard navigation (arrow keys, `?` shortcut), and comprehensive coverage of every feature ### Performance & Fixes - **Lazy Loading & Virtualisation** (#194) — Virtual windowing for conversation history, markdown memoisation, and debounced search for smooth rendering of large sessions - **Ctrl+C Copy Fix** (#195) — `Ctrl+C` now copies selected text as expected; interrupt-Claude behaviour only fires when no text is selected ### UX - Back-to-workflow button in PRD Creator and Task Loop panels for easy navigation - Navigation icon cluster replaced with a single clean dropdown menu ## Closes Closes #179 Closes #180 Closes #181 Closes #188 Closes #189 Closes #190 Closes #191 Closes #192 Closes #193 Closes #194 Closes #195 Closes #196 --- ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: #197 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
154 lines
5.3 KiB
Svelte
154 lines
5.3 KiB
Svelte
<script lang="ts">
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { openUrl } from "@tauri-apps/plugin-opener";
|
|
import { getVersion } from "@tauri-apps/api/app";
|
|
import { onMount } from "svelte";
|
|
import type { ChangelogEntry } from "$lib/types/messages";
|
|
import Markdown from "./Markdown.svelte";
|
|
|
|
interface Props {
|
|
onClose: () => void;
|
|
}
|
|
|
|
const { onClose }: Props = $props();
|
|
|
|
let entries = $state<ChangelogEntry[]>([]);
|
|
let loading = $state(true);
|
|
let error = $state<string | null>(null);
|
|
let currentVersion = $state("");
|
|
|
|
export function formatReleaseDate(isoString: string): string {
|
|
if (!isoString) return "Unknown date";
|
|
const date = new Date(isoString);
|
|
if (isNaN(date.getTime())) return "Unknown date";
|
|
return date.toLocaleDateString("en-GB", {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
timeZone: "UTC",
|
|
});
|
|
}
|
|
|
|
async function loadChangelog(): Promise<void> {
|
|
loading = true;
|
|
error = null;
|
|
try {
|
|
entries = await invoke<ChangelogEntry[]>("fetch_changelog");
|
|
} catch (err) {
|
|
error = err instanceof Error ? err.message : String(err);
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
|
|
onMount(async () => {
|
|
currentVersion = await getVersion();
|
|
await loadChangelog();
|
|
});
|
|
</script>
|
|
|
|
<div
|
|
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
|
|
onclick={onClose}
|
|
role="button"
|
|
tabindex="0"
|
|
onkeydown={(e) => e.key === "Escape" && onClose()}
|
|
>
|
|
<div
|
|
class="bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg shadow-xl max-w-2xl w-full max-h-[80vh] overflow-hidden flex flex-col"
|
|
onclick={(e) => e.stopPropagation()}
|
|
onkeydown={(e) => e.stopPropagation()}
|
|
role="dialog"
|
|
aria-labelledby="changelog-title"
|
|
tabindex="-1"
|
|
>
|
|
<div class="flex items-center justify-between p-6 pb-4 border-b border-[var(--border-color)]">
|
|
<h2 id="changelog-title" class="text-xl font-semibold text-[var(--text-primary)]">
|
|
Changelog
|
|
</h2>
|
|
<button
|
|
onclick={onClose}
|
|
class="p-1 text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors"
|
|
aria-label="Close"
|
|
>
|
|
<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>
|
|
|
|
<div class="overflow-y-auto flex-1 p-6">
|
|
{#if loading}
|
|
<div class="flex items-center justify-center py-12">
|
|
<div
|
|
class="w-8 h-8 border-2 border-[var(--accent-primary)] border-t-transparent rounded-full animate-spin"
|
|
></div>
|
|
<span class="ml-3 text-[var(--text-secondary)]">Fetching releases...</span>
|
|
</div>
|
|
{:else if error}
|
|
<div class="text-center py-12">
|
|
<p class="text-red-400 mb-4">{error}</p>
|
|
<button onclick={loadChangelog} class="btn-trans-gradient px-4 py-2 rounded text-sm">
|
|
Retry
|
|
</button>
|
|
</div>
|
|
{:else if entries.length === 0}
|
|
<p class="text-center text-[var(--text-secondary)] py-12">No releases found.</p>
|
|
{:else}
|
|
<div class="space-y-6">
|
|
{#each entries as entry (entry.version)}
|
|
<div class="border border-[var(--border-color)] rounded-lg overflow-hidden">
|
|
<div
|
|
class="flex flex-wrap items-center gap-2 px-4 py-3 bg-[var(--bg-secondary)] border-b border-[var(--border-color)]"
|
|
>
|
|
<span
|
|
class="font-mono font-semibold text-sm {entry.version === `v${currentVersion}`
|
|
? 'text-[var(--trans-pink)]'
|
|
: 'text-[var(--text-primary)]'}"
|
|
>
|
|
{entry.version}
|
|
</span>
|
|
{#if entry.version === `v${currentVersion}`}
|
|
<span
|
|
class="text-xs px-2 py-0.5 rounded-full bg-[var(--trans-pink)]/20 text-[var(--trans-pink)] border border-[var(--trans-pink)]/30"
|
|
>
|
|
current
|
|
</span>
|
|
{/if}
|
|
{#if entry.prerelease}
|
|
<span
|
|
class="text-xs px-2 py-0.5 rounded-full bg-yellow-500/20 text-yellow-400 border border-yellow-500/30"
|
|
>
|
|
pre-release
|
|
</span>
|
|
{/if}
|
|
<span class="ml-auto text-xs text-[var(--text-muted)]">
|
|
{formatReleaseDate(entry.created_at)}
|
|
</span>
|
|
<button
|
|
onclick={() => openUrl(entry.url)}
|
|
class="text-xs text-[var(--accent-primary)] hover:text-[var(--accent-primary-hover)] transition-colors underline"
|
|
>
|
|
View on Gitea
|
|
</button>
|
|
</div>
|
|
{#if entry.notes}
|
|
<div class="p-4 text-sm text-[var(--text-secondary)]">
|
|
<Markdown content={entry.notes} />
|
|
</div>
|
|
{:else}
|
|
<p class="p-4 text-xs text-[var(--text-muted)] italic">No release notes.</p>
|
|
{/if}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|