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>
100 lines
2.4 KiB
TypeScript
100 lines
2.4 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { HELP_PAGES, nextPage, prevPage, clampPage, isFirstPage, isLastPage } from "./helpPages";
|
|
|
|
describe("HELP_PAGES", () => {
|
|
it("contains 12 pages", () => {
|
|
expect(HELP_PAGES).toHaveLength(12);
|
|
});
|
|
|
|
it("has unique ids", () => {
|
|
const ids = HELP_PAGES.map((p) => p.id);
|
|
expect(new Set(ids).size).toBe(ids.length);
|
|
});
|
|
|
|
it("has non-empty titles", () => {
|
|
for (const page of HELP_PAGES) {
|
|
expect(page.title.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("nextPage", () => {
|
|
it("advances to the next page", () => {
|
|
expect(nextPage(0, 7)).toBe(1);
|
|
expect(nextPage(3, 7)).toBe(4);
|
|
});
|
|
|
|
it("does not go past the last page", () => {
|
|
expect(nextPage(6, 7)).toBe(6);
|
|
});
|
|
|
|
it("clamps when already at the last page", () => {
|
|
expect(nextPage(10, 7)).toBe(6);
|
|
});
|
|
});
|
|
|
|
describe("prevPage", () => {
|
|
it("goes back to the previous page", () => {
|
|
expect(prevPage(3)).toBe(2);
|
|
expect(prevPage(1)).toBe(0);
|
|
});
|
|
|
|
it("does not go before the first page", () => {
|
|
expect(prevPage(0)).toBe(0);
|
|
});
|
|
|
|
it("clamps when already at the first page", () => {
|
|
expect(prevPage(-1)).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("clampPage", () => {
|
|
it("returns the page unchanged when in range", () => {
|
|
expect(clampPage(3, 7)).toBe(3);
|
|
expect(clampPage(0, 7)).toBe(0);
|
|
expect(clampPage(6, 7)).toBe(6);
|
|
});
|
|
|
|
it("clamps negative indices to 0", () => {
|
|
expect(clampPage(-1, 7)).toBe(0);
|
|
});
|
|
|
|
it("clamps over-range indices to the last page", () => {
|
|
expect(clampPage(10, 7)).toBe(6);
|
|
});
|
|
|
|
it("returns 0 when totalPages is 0", () => {
|
|
expect(clampPage(3, 0)).toBe(0);
|
|
});
|
|
|
|
it("returns 0 when totalPages is negative", () => {
|
|
expect(clampPage(3, -1)).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("isFirstPage", () => {
|
|
it("returns true for index 0", () => {
|
|
expect(isFirstPage(0)).toBe(true);
|
|
});
|
|
|
|
it("returns false for index greater than 0", () => {
|
|
expect(isFirstPage(1)).toBe(false);
|
|
expect(isFirstPage(6)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("isLastPage", () => {
|
|
it("returns true for the last index", () => {
|
|
expect(isLastPage(6, 7)).toBe(true);
|
|
});
|
|
|
|
it("returns false for indices before the last", () => {
|
|
expect(isLastPage(5, 7)).toBe(false);
|
|
expect(isLastPage(0, 7)).toBe(false);
|
|
});
|
|
|
|
it("returns true when index exceeds total", () => {
|
|
expect(isLastPage(10, 7)).toBe(true);
|
|
});
|
|
});
|