generated from nhcarrigan/template
feat: productivity suite — task loop, workflow, theming, docs & more (#197)
## 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>
This commit was merged in pull request #197.
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
import { writable } from "svelte/store";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
export type ProjectFile = "PROJECT" | "REQUIREMENTS" | "ROADMAP" | "STATE" | "CODEBASE";
|
||||
|
||||
export const PROJECT_FILE_NAMES: Record<ProjectFile, string> = {
|
||||
PROJECT: "PROJECT.md",
|
||||
REQUIREMENTS: "REQUIREMENTS.md",
|
||||
ROADMAP: "ROADMAP.md",
|
||||
STATE: "STATE.md",
|
||||
CODEBASE: "CODEBASE.md",
|
||||
};
|
||||
|
||||
export const PROJECT_TEMPLATES: Record<ProjectFile, string> = {
|
||||
PROJECT: `# Project Overview
|
||||
|
||||
## What is this project?
|
||||
|
||||
## Goals
|
||||
|
||||
## Tech Stack
|
||||
|
||||
## Architecture
|
||||
`,
|
||||
REQUIREMENTS: `# Requirements
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
## Out of Scope
|
||||
`,
|
||||
ROADMAP: `# Roadmap
|
||||
|
||||
## Current Sprint
|
||||
|
||||
## Next Sprint
|
||||
|
||||
## Backlog
|
||||
|
||||
## Completed
|
||||
`,
|
||||
STATE: `# Current State
|
||||
|
||||
## Last Updated
|
||||
|
||||
## What's Working
|
||||
|
||||
## In Progress
|
||||
|
||||
## Known Issues
|
||||
|
||||
## Next Steps
|
||||
`,
|
||||
CODEBASE: "",
|
||||
};
|
||||
|
||||
const PROJECT_FILES = Object.keys(PROJECT_FILE_NAMES) as ProjectFile[];
|
||||
|
||||
export interface ProjectScan {
|
||||
working_dir: string;
|
||||
file_tree: string;
|
||||
detected_type: string;
|
||||
key_files: string[];
|
||||
}
|
||||
|
||||
function createProjectContextStore() {
|
||||
const contents = writable<Record<ProjectFile, string | null>>({
|
||||
PROJECT: null,
|
||||
REQUIREMENTS: null,
|
||||
ROADMAP: null,
|
||||
STATE: null,
|
||||
CODEBASE: null,
|
||||
});
|
||||
|
||||
const isLoading = writable<Record<ProjectFile, boolean>>({
|
||||
PROJECT: false,
|
||||
REQUIREMENTS: false,
|
||||
ROADMAP: false,
|
||||
STATE: false,
|
||||
CODEBASE: false,
|
||||
});
|
||||
|
||||
const isSaving = writable<Record<ProjectFile, boolean>>({
|
||||
PROJECT: false,
|
||||
REQUIREMENTS: false,
|
||||
ROADMAP: false,
|
||||
STATE: false,
|
||||
CODEBASE: false,
|
||||
});
|
||||
|
||||
const activeFile = writable<ProjectFile>("PROJECT");
|
||||
const isMappingCodebase = writable<boolean>(false);
|
||||
|
||||
async function loadFile(file: ProjectFile, workingDirectory: string): Promise<void> {
|
||||
isLoading.update((state) => ({ ...state, [file]: true }));
|
||||
try {
|
||||
const path = `${workingDirectory}/${PROJECT_FILE_NAMES[file]}`;
|
||||
const content = await invoke<string>("read_file_content", { path });
|
||||
contents.update((state) => ({ ...state, [file]: content }));
|
||||
} catch {
|
||||
contents.update((state) => ({ ...state, [file]: null }));
|
||||
} finally {
|
||||
isLoading.update((state) => ({ ...state, [file]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
async function saveFile(
|
||||
file: ProjectFile,
|
||||
content: string,
|
||||
workingDirectory: string
|
||||
): Promise<boolean> {
|
||||
isSaving.update((state) => ({ ...state, [file]: true }));
|
||||
try {
|
||||
const path = `${workingDirectory}/${PROJECT_FILE_NAMES[file]}`;
|
||||
await invoke("write_file_content", { path, content });
|
||||
contents.update((state) => ({ ...state, [file]: content }));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Failed to save project context file:", error);
|
||||
return false;
|
||||
} finally {
|
||||
isSaving.update((state) => ({ ...state, [file]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAll(workingDirectory: string): Promise<void> {
|
||||
await Promise.all(PROJECT_FILES.map((file) => loadFile(file, workingDirectory)));
|
||||
}
|
||||
|
||||
function setActiveFile(file: ProjectFile): void {
|
||||
activeFile.set(file);
|
||||
}
|
||||
|
||||
function getTemplate(file: ProjectFile): string {
|
||||
return PROJECT_TEMPLATES[file];
|
||||
}
|
||||
|
||||
async function mapCodebase(workingDirectory: string, conversationId: string): Promise<void> {
|
||||
isMappingCodebase.set(true);
|
||||
try {
|
||||
const scan = await invoke<ProjectScan>("scan_project", {
|
||||
workingDir: workingDirectory,
|
||||
});
|
||||
|
||||
const prompt = buildCodebaseMapPrompt(scan);
|
||||
await invoke("send_prompt", { conversationId, message: prompt });
|
||||
} catch (error) {
|
||||
console.error("Failed to map codebase:", error);
|
||||
isMappingCodebase.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
function finishMapping(): void {
|
||||
isMappingCodebase.set(false);
|
||||
}
|
||||
|
||||
return {
|
||||
contents: { subscribe: contents.subscribe },
|
||||
isLoading: { subscribe: isLoading.subscribe },
|
||||
isSaving: { subscribe: isSaving.subscribe },
|
||||
activeFile: { subscribe: activeFile.subscribe },
|
||||
isMappingCodebase: { subscribe: isMappingCodebase.subscribe },
|
||||
loadFile,
|
||||
saveFile,
|
||||
loadAll,
|
||||
setActiveFile,
|
||||
getTemplate,
|
||||
mapCodebase,
|
||||
finishMapping,
|
||||
};
|
||||
}
|
||||
|
||||
function buildCodebaseMapPrompt(scan: ProjectScan): string {
|
||||
const keyFilesSection =
|
||||
scan.key_files.length > 0
|
||||
? `\n\nKey files detected:\n${scan.key_files.map((f) => `- ${f}`).join("\n")}`
|
||||
: "";
|
||||
|
||||
return `Please analyse this codebase and generate a comprehensive \`CODEBASE.md\` file in the working directory (${scan.working_dir}).
|
||||
|
||||
Project type detected: **${scan.detected_type}**${keyFilesSection}
|
||||
|
||||
Directory structure:
|
||||
\`\`\`
|
||||
${scan.file_tree}
|
||||
\`\`\`
|
||||
|
||||
The CODEBASE.md file should include:
|
||||
1. **Overview** — what the project does and its purpose
|
||||
2. **Architecture** — key directories, how the code is organised, and the overall structure
|
||||
3. **Key Components** — the most important files and modules, what they do, and how they interact
|
||||
4. **Data Flow** — how data moves through the system (if applicable)
|
||||
5. **Dependencies** — notable external dependencies and why they are used
|
||||
6. **Development Notes** — anything helpful for a developer new to the codebase
|
||||
|
||||
Write the file concisely but thoroughly. Focus on information that helps a developer understand the codebase quickly. Use the actual file structure above to inform your analysis — read the key files as needed before writing.`;
|
||||
}
|
||||
|
||||
export const projectContextStore = createProjectContextStore();
|
||||
|
||||
// Signal store for injecting context into the active InputBar.
|
||||
// StatusBar sets this; InputBar subscribes and applies it to inputValue directly,
|
||||
// then resets it to null so the signal only fires once.
|
||||
export const injectTextStore = writable<string | null>(null);
|
||||
|
||||
// Appended silently to custom_instructions at connection time (never saved to config).
|
||||
// Mirrors how CLAUDE.md works natively — Claude checks the files itself if they exist.
|
||||
export const PROJECT_CONTEXT_SYSTEM_ADDENDUM = `
|
||||
|
||||
---
|
||||
The following project context files may exist in your working directory. If they exist, read and refer to them as needed:
|
||||
- PROJECT.md — project overview, goals, and architecture
|
||||
- REQUIREMENTS.md — functional and non-functional requirements
|
||||
- ROADMAP.md — current sprint, backlog, and completed work
|
||||
- STATE.md — current state, known issues, and next steps
|
||||
- CODEBASE.md — auto-generated codebase map and architecture overview`;
|
||||
Reference in New Issue
Block a user