mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
Add settings
This commit is contained in:
parent
99e8ada944
commit
fb1750fc0d
9 changed files with 627 additions and 72 deletions
559
docs/projects-plus-phase6-development.md
Normal file
559
docs/projects-plus-phase6-development.md
Normal file
|
|
@ -0,0 +1,559 @@
|
|||
# Projects+ Phase 6: Goal Completion & Settings - Development Document
|
||||
|
||||
## Overview
|
||||
|
||||
Phase 6 completes the Projects+ MVP by implementing goal completion workflows with reflection, archived goal views, and a dedicated settings tab. This document provides detailed implementation guidance, edge cases, and UI specifications.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Goal Completion Flow](#1-goal-completion-flow)
|
||||
2. [Archived Goals View](#2-archived-goals-view)
|
||||
3. [Projects+ Settings Tab](#3-projects-settings-tab)
|
||||
4. [Implementation Details](#4-implementation-details)
|
||||
5. [Edge Cases & Error Handling](#5-edge-cases--error-handling)
|
||||
6. [Testing Checklist](#6-testing-checklist)
|
||||
|
||||
---
|
||||
|
||||
## 1. Goal Completion Flow
|
||||
|
||||
### 1.1 Triggering the Completion Modal
|
||||
|
||||
The completion modal can be triggered from multiple locations:
|
||||
|
||||
#### Entry Points
|
||||
|
||||
| Location | UI Element | Action |
|
||||
| ---------------------------- | --------------------------- | ---------------------------------------------------- |
|
||||
| **ProjectDetail Footer** | "Complete" button (primary) | Opens `ProjectStatusDialog` with `action="complete"` |
|
||||
| **ProjectCard Context Menu** | "Complete" menu item | Opens `ProjectStatusDialog` with `action="complete"` |
|
||||
| **ProjectCard Quick Action** | Checkmark icon button | Opens `ProjectStatusDialog` with `action="complete"` |
|
||||
|
||||
#### Current Implementation
|
||||
|
||||
The completion flow is already wired in `ProjectDetail.tsx`:
|
||||
|
||||
```typescript
|
||||
// ProjectDetail.tsx:430-431
|
||||
<Button onClick={() => handleStatusAction("complete")}>
|
||||
<Check className="tw-mr-2 tw-size-4" />
|
||||
Complete
|
||||
</Button>
|
||||
```
|
||||
|
||||
This triggers `handleStatusAction("complete")` which opens `ProjectStatusDialog`:
|
||||
|
||||
```typescript
|
||||
// ProjectDetail.tsx:135-138
|
||||
const handleStatusAction = useCallback((action: StatusAction) => {
|
||||
setStatusAction(action);
|
||||
setStatusDialogOpen(true);
|
||||
}, []);
|
||||
```
|
||||
|
||||
### 1.2 Completion Modal UI Specification
|
||||
|
||||
The `ProjectStatusDialog` renders different content based on the action type. For completion:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Complete Project [X] │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ You've been working on this project for **5 days** │ │
|
||||
│ │ with **12 notes** and **3 conversations**. │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Reflection (optional) │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ What did you learn or accomplish? │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Cancel] [Complete Project] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Journey Summary Component
|
||||
|
||||
The journey summary uses `calculateJourneySummary()` from `utils.ts`:
|
||||
|
||||
```typescript
|
||||
// utils.ts
|
||||
export function calculateJourneySummary(project: Project) {
|
||||
const now = Date.now();
|
||||
const daysActive = Math.ceil((now - project.createdAt) / (1000 * 60 * 60 * 24));
|
||||
|
||||
return {
|
||||
daysActive,
|
||||
notesCount: project.notes.length,
|
||||
conversationsCount: project.conversations.length,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 Completion Confirmation Handler
|
||||
|
||||
```typescript
|
||||
// ProjectDetail.tsx:140-155
|
||||
const handleConfirmStatus = useCallback(
|
||||
async (reflection?: string) => {
|
||||
switch (statusAction) {
|
||||
case "complete":
|
||||
await plugin.projectsPlusManager.completeProject(projectId, reflection);
|
||||
break;
|
||||
case "archive":
|
||||
await plugin.projectsPlusManager.archiveProject(projectId);
|
||||
break;
|
||||
case "reactivate":
|
||||
await plugin.projectsPlusManager.reactivateProject(projectId);
|
||||
break;
|
||||
}
|
||||
},
|
||||
[plugin.projectsPlusManager, projectId, statusAction]
|
||||
);
|
||||
```
|
||||
|
||||
### 1.4 Project Status Transitions
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ active │
|
||||
└──────────────┘
|
||||
│
|
||||
┌─────────┴─────────┐
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ completed │ │ archived │
|
||||
└──────────────┘ └──────────────┘
|
||||
│ │
|
||||
└─────────┬─────────┘
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ active │ (reactivate)
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Archived Goals View
|
||||
|
||||
### 2.1 Goal List Filtering Behavior
|
||||
|
||||
**Design Decision:** Completed goals are NOT shown in the main list by default. Users must use the status filter dropdown to view them.
|
||||
|
||||
The `ProjectsPanel` already implements this via a status filter:
|
||||
|
||||
```typescript
|
||||
// ProjectsPanel.tsx:167-176
|
||||
<select value={filter} onChange={(e) => setFilter(e.target.value as FilterStatus)}>
|
||||
<option value="all">All</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="archived">Archived</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
### 2.2 Default Filter Behavior
|
||||
|
||||
The default filter is `"all"` which shows all projects. However, projects are sorted by `updatedAt` descending, so active projects with recent activity appear first.
|
||||
|
||||
**Implementation Note:** There is no separate setting for "show completed in list" - the filter dropdown provides this functionality directly.
|
||||
|
||||
### 2.3 Completed/Archived Project Detail View
|
||||
|
||||
When viewing a completed or archived project, the UI should reflect the read-only nature:
|
||||
|
||||
#### Visual Indicators
|
||||
|
||||
1. **Status Badge** - Shows "Completed" or "Archived" with appropriate styling
|
||||
2. **Reflection Section** - Displays the reflection text (if provided)
|
||||
3. **Disabled Actions** - "Add Notes", "Suggest Notes" buttons hidden for non-active projects
|
||||
|
||||
Current implementation in `ProjectDetail.tsx`:
|
||||
|
||||
```typescript
|
||||
// Notes section actions - only shown for active projects
|
||||
{project.status === "active" && (
|
||||
<div className="tw-flex tw-gap-1">
|
||||
<Button variant="ghost" size="sm" onClick={() => setSuggestDialogOpen(true)}>
|
||||
<Sparkles className="tw-size-3" />
|
||||
Suggest
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={handleOpenAddNoteModal}>
|
||||
<Plus className="tw-size-3" />
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
#### Footer Actions by Status
|
||||
|
||||
| Status | Available Actions |
|
||||
| ----------- | ------------------------- |
|
||||
| `active` | Edit, Archive, Complete |
|
||||
| `completed` | Edit, Archive, Reactivate |
|
||||
| `archived` | Edit, Reactivate |
|
||||
|
||||
### 2.4 ProjectStatusBadge Component
|
||||
|
||||
Create a reusable badge component for consistent status display:
|
||||
|
||||
```typescript
|
||||
// src/components/projects-plus/ProjectStatusBadge.tsx
|
||||
interface ProjectStatusBadgeProps {
|
||||
status: ProjectStatus;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ProjectStatusBadge({ status, className }: ProjectStatusBadgeProps) {
|
||||
return (
|
||||
<span className={cn(
|
||||
"tw-rounded-sm tw-px-2 tw-py-0.5 tw-text-xs tw-font-medium tw-capitalize",
|
||||
getStatusBadgeStyles(status),
|
||||
className
|
||||
)}>
|
||||
{status}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The `getStatusBadgeStyles` function from `utils.ts`:
|
||||
|
||||
```typescript
|
||||
export function getStatusBadgeStyles(status: ProjectStatus): string {
|
||||
switch (status) {
|
||||
case "active":
|
||||
return "tw-bg-modifier-success-rgb/20 tw-text-success";
|
||||
case "completed":
|
||||
return "tw-bg-blue-rgb/20 tw-text-context-manager-blue";
|
||||
case "archived":
|
||||
return "tw-bg-modifier-hover tw-text-muted";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Projects+ Settings Tab
|
||||
|
||||
### 3.1 Settings Tab Location
|
||||
|
||||
Add a new tab to `SettingsMainV2.tsx` OR integrate into an existing tab.
|
||||
|
||||
**Recommended Approach:** Integrate into the existing settings structure rather than adding a new tab. Projects+ settings should be part of the "Plus" or "Advanced" tab since Projects+ is a premium feature.
|
||||
|
||||
### 3.2 Settings Interface
|
||||
|
||||
Based on the design decisions:
|
||||
|
||||
```typescript
|
||||
// Settings that will be added to CopilotSettings
|
||||
interface ProjectsPlusSettings {
|
||||
/** Folder path for Projects+ data */
|
||||
projectsPlusFolder: string; // Already exists, default: "copilot/projects"
|
||||
|
||||
// REMOVED SETTINGS:
|
||||
// projectsPlusEnabled: boolean; - REMOVED (feature always enabled)
|
||||
// autoSaveEnabled: boolean; - REMOVED (always enabled)
|
||||
// defaultDiscussModel: string; - REMOVED (uses main model)
|
||||
// showCompletedInList: boolean; - REMOVED (use filter dropdown instead)
|
||||
// noteSuggestionCount: number; - REMOVED (handled by search system)
|
||||
// excludedFolders: string[]; - REMOVED (follows main qaExclusions setting)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Settings Tab UI Design
|
||||
|
||||
Since most settings have been consolidated, the Projects+ settings section is minimal:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Projects+ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Projects Folder │
|
||||
│ Folder where project data is stored. │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
||||
│ │ copilot/projects ││
|
||||
│ └─────────────────────────────────────────────────────────────┘│
|
||||
│ │
|
||||
│ ───────────────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ ℹ️ Note: Projects+ uses the same inclusion/exclusion │
|
||||
│ settings as QA Mode for note suggestions. │
|
||||
│ Configure them in the "QA" settings tab. │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.4 ProjectsPlusSettings Component Implementation
|
||||
|
||||
```typescript
|
||||
// src/settings/v2/components/ProjectsPlusSettings.tsx
|
||||
|
||||
import { SettingItem } from "@/components/ui/setting-item";
|
||||
import { updateSetting, useSettingsValue } from "@/settings/model";
|
||||
import React from "react";
|
||||
|
||||
export const ProjectsPlusSettings: React.FC = () => {
|
||||
const settings = useSettingsValue();
|
||||
|
||||
return (
|
||||
<div className="tw-space-y-4">
|
||||
<section>
|
||||
<div className="tw-mb-3 tw-text-xl tw-font-bold">Projects+</div>
|
||||
<div className="tw-space-y-4">
|
||||
{/* Projects Folder */}
|
||||
<SettingItem
|
||||
type="text"
|
||||
title="Projects Folder"
|
||||
description="Folder where project data is stored. Each project creates a subfolder here."
|
||||
value={settings.projectsPlusFolder}
|
||||
onChange={(value) => updateSetting("projectsPlusFolder", value)}
|
||||
placeholder="copilot/projects"
|
||||
/>
|
||||
|
||||
{/* Info about shared settings */}
|
||||
<div className="tw-rounded tw-bg-secondary tw-p-3 tw-text-sm tw-text-muted">
|
||||
<p>
|
||||
<strong>Note:</strong> Projects+ uses the same inclusion/exclusion settings as QA Mode
|
||||
for note suggestions. Configure them in the <strong>QA</strong> settings tab.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 3.5 Integrating into Settings
|
||||
|
||||
**Option A: Add to CopilotPlusSettings.tsx**
|
||||
|
||||
Since Projects+ is a Plus feature, add it to the existing Plus settings tab:
|
||||
|
||||
```typescript
|
||||
// In CopilotPlusSettings.tsx, add:
|
||||
import { ProjectsPlusSettings } from "./ProjectsPlusSettings";
|
||||
|
||||
// In the component render:
|
||||
<ProjectsPlusSettings />
|
||||
```
|
||||
|
||||
**Option B: Add to AdvancedSettings.tsx**
|
||||
|
||||
Alternatively, add to Advanced settings as a separate section.
|
||||
|
||||
### 3.6 Note Suggestion Configuration
|
||||
|
||||
**Key Design Decision:** Note suggestions follow the main QA inclusion/exclusion settings.
|
||||
|
||||
The `NoteAssignmentService` should use `qaExclusions` and `qaInclusions` from the main settings:
|
||||
|
||||
```typescript
|
||||
// In NoteAssignmentService.ts
|
||||
import { getSettings } from "@/settings/model";
|
||||
|
||||
class NoteAssignmentService {
|
||||
async suggestNotes(projectDescription: string, options?: NoteAssignmentOptions) {
|
||||
const settings = getSettings();
|
||||
|
||||
// Apply the same exclusions as QA mode
|
||||
const exclusions = settings.qaExclusions;
|
||||
const inclusions = settings.qaInclusions;
|
||||
|
||||
// ... use these for filtering note suggestions
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementation Details
|
||||
|
||||
### 4.1 Naming Convention Update
|
||||
|
||||
**Important:** The original design document (`projects-plus-technical-design.md`) uses "Goal" terminology (e.g., `GoalStatusBadge.tsx`, `GoalCompletion.tsx`). However, the actual implementation uses "Project" terminology throughout. This phase should continue using "Project" naming:
|
||||
|
||||
| Original Design Name | Actual Implementation Name |
|
||||
| --------------------- | ----------------------------------------------------- |
|
||||
| `GoalStatusBadge.tsx` | `ProjectStatusBadge.tsx` |
|
||||
| `GoalCompletion.tsx` | `ProjectStatusDialog.tsx` (already exists) |
|
||||
| `ArchivedGoals.tsx` | Not needed (handled by filter in `ProjectsPanel.tsx`) |
|
||||
|
||||
### 4.2 Files to Create
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------------------------------------- | ------------------------------- |
|
||||
| `src/settings/v2/components/ProjectsPlusSettings.tsx` | Settings tab component |
|
||||
| `src/components/projects-plus/ProjectStatusBadge.tsx` | Reusable status badge component |
|
||||
|
||||
### 4.3 Files to Modify
|
||||
|
||||
| File | Changes |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `src/settings/model.ts` | Remove `projectsPlusEnabled` from `CopilotSettings` interface |
|
||||
| `src/constants.ts` | Remove `projectsPlusEnabled` from `DEFAULT_SETTINGS` |
|
||||
| `src/main.ts` | Remove any conditional checks for `projectsPlusEnabled` |
|
||||
| `src/settings/v2/components/CopilotPlusSettings.tsx` | Import and render `ProjectsPlusSettings` |
|
||||
| `src/core/projects-plus/NoteAssignmentService.ts` | Use `qaExclusions`/`qaInclusions` for filtering |
|
||||
| `src/components/projects-plus/ProjectDetail.tsx` | Replace inline badge with `ProjectStatusBadge` component |
|
||||
| `src/components/projects-plus/ProjectCard.tsx` | Replace inline badge with `ProjectStatusBadge` component |
|
||||
|
||||
### 4.4 Conversation Auto-Save Behavior
|
||||
|
||||
Conversations are auto-saved per message exchange. This is NOT configurable - it's always enabled.
|
||||
|
||||
The save happens in `ConversationPersistence.ts`:
|
||||
|
||||
```typescript
|
||||
// After each message exchange:
|
||||
await conversationPersistence.saveConversation(project, conversation);
|
||||
```
|
||||
|
||||
### 4.5 Model Configuration for Discuss
|
||||
|
||||
The Discuss feature uses the default chat model (`defaultModelKey`) rather than a separate model setting. This simplifies configuration and ensures consistency.
|
||||
|
||||
---
|
||||
|
||||
## 5. Edge Cases & Error Handling
|
||||
|
||||
### 5.1 Completion Edge Cases
|
||||
|
||||
| Scenario | Handling |
|
||||
| ---------------------------------- | ------------------------------------------------------------ |
|
||||
| **Empty reflection** | Allow completion - reflection is optional |
|
||||
| **Very long reflection** | No limit, but consider adding a soft warning for >1000 chars |
|
||||
| **Completion fails** | Show error notice, keep dialog open |
|
||||
| **Project already completed** | Button should not be visible; if API called, return silently |
|
||||
| **Concurrent completion attempts** | Use loading state to prevent double-submission |
|
||||
|
||||
### 5.2 Settings Edge Cases
|
||||
|
||||
| Scenario | Handling |
|
||||
| -------------------------------------- | -------------------------------------------------------- |
|
||||
| **Invalid folder path** | Validate path, show error if contains illegal characters |
|
||||
| **Folder doesn't exist** | Create folder on first project creation, not on save |
|
||||
| **Empty folder path** | Fall back to default `copilot/projects` |
|
||||
| **Path with leading/trailing slashes** | Normalize path on save |
|
||||
|
||||
### 5.3 Filter Edge Cases
|
||||
|
||||
| Scenario | Handling |
|
||||
| --------------------------------------------- | ----------------------------------------------------------- |
|
||||
| **Filter shows no results** | Show empty state with helpful message |
|
||||
| **Search + filter combination** | Apply both filters, show combined empty state if no results |
|
||||
| **Project status changes while viewing list** | Reactive update via subscription |
|
||||
|
||||
### 5.4 Archived Goal View Edge Cases
|
||||
|
||||
| Scenario | Handling |
|
||||
| -------------------------------------------- | -------------------------------------------------- |
|
||||
| **Viewing archived goal with deleted notes** | Show warning icon next to missing notes |
|
||||
| **Attempting to discuss archived goal** | "Discuss" button hidden for archived status |
|
||||
| **Reactivating a goal** | Status changes to "active", all actions re-enabled |
|
||||
|
||||
---
|
||||
|
||||
## 6. Testing Checklist
|
||||
|
||||
### 6.1 Goal Completion Flow
|
||||
|
||||
- [ ] Click "Complete" from ProjectDetail footer opens dialog
|
||||
- [ ] Journey summary shows correct days, notes, conversations counts
|
||||
- [ ] Empty reflection is accepted
|
||||
- [ ] Reflection with text is saved to project
|
||||
- [ ] Cancel closes dialog without changes
|
||||
- [ ] Completing updates project status to "completed"
|
||||
- [ ] Completed project shows in list when filter is "All" or "Completed"
|
||||
- [ ] Completed project does NOT appear when filter is "Active"
|
||||
- [ ] `goal.md` file contains `status: "completed"` and `completedAt` timestamp
|
||||
- [ ] `goal.md` file contains reflection text
|
||||
|
||||
### 6.2 Archived Goals View
|
||||
|
||||
- [ ] Archived projects appear in list when filter is "All" or "Archived"
|
||||
- [ ] Archived project detail shows "Archived" badge
|
||||
- [ ] "Add Notes" and "Suggest" buttons hidden for archived projects
|
||||
- [ ] Footer shows only "Edit" and "Reactivate" for archived projects
|
||||
- [ ] Reactivating changes status back to "active"
|
||||
- [ ] All actions re-enabled after reactivation
|
||||
|
||||
### 6.3 Settings Tab
|
||||
|
||||
- [ ] Projects+ settings appear in Plus/Advanced tab
|
||||
- [ ] Folder path input accepts valid paths
|
||||
- [ ] Info message about shared QA settings is visible
|
||||
- [ ] Settings persist after Obsidian reload
|
||||
- [ ] Changing folder path affects new project creation
|
||||
|
||||
### 6.4 Note Suggestions with Exclusions
|
||||
|
||||
- [ ] Notes in excluded folders (from QA settings) don't appear in suggestions
|
||||
- [ ] Notes matching inclusion patterns appear in suggestions
|
||||
- [ ] Exclusions take precedence over inclusions
|
||||
- [ ] Suggestions work correctly with empty exclusions/inclusions
|
||||
|
||||
### 6.5 Persistence
|
||||
|
||||
- [ ] Create project, complete it, reload Obsidian - project still completed
|
||||
- [ ] Reflection text persists after reload
|
||||
- [ ] Settings persist after reload
|
||||
- [ ] Projects folder setting affects file storage location
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: ProjectStatusBadge Styles Reference
|
||||
|
||||
```typescript
|
||||
// Current implementation in utils.ts (used by ProjectStatusBadge)
|
||||
export function getStatusBadgeStyles(status: ProjectStatus): string {
|
||||
switch (status) {
|
||||
case "active":
|
||||
return "tw-bg-modifier-success-rgb/20 tw-text-success";
|
||||
case "completed":
|
||||
return "tw-bg-blue-rgb/20 tw-text-context-manager-blue";
|
||||
case "archived":
|
||||
return "tw-bg-modifier-hover tw-text-muted";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Appendix B: Default Settings Values
|
||||
|
||||
```typescript
|
||||
// From constants.ts
|
||||
export const DEFAULT_PROJECTS_FOLDER = "copilot/projects";
|
||||
|
||||
// From DEFAULT_SETTINGS
|
||||
projectsPlusFolder: DEFAULT_PROJECTS_FOLDER,
|
||||
|
||||
// Note: projectsPlusEnabled has been removed - feature is always enabled
|
||||
```
|
||||
|
||||
## Appendix C: Removed Settings Rationale
|
||||
|
||||
| Removed Setting | Reason |
|
||||
| --------------------- | ----------------------------------------------------- |
|
||||
| `projectsPlusEnabled` | Feature is always enabled, no toggle needed |
|
||||
| `showCompletedInList` | Filter dropdown provides this functionality directly |
|
||||
| `noteSuggestionCount` | Handled by search system, no user-facing limit needed |
|
||||
| `excludedFolders` | Consolidated with main `qaExclusions` setting |
|
||||
| `autoSaveEnabled` | Always enabled per design decision |
|
||||
| `defaultDiscussModel` | Uses main `defaultModelKey` for consistency |
|
||||
|
||||
This consolidation reduces settings complexity and ensures consistent behavior across the plugin.
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { Project, ProjectStatus } from "@/types/projects-plus";
|
||||
import { Project } from "@/types/projects-plus";
|
||||
import { Check, Edit, FileText, MessageSquare, MoreVertical, Trash2 } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import { ProjectStatusBadge } from "./ProjectStatusBadge";
|
||||
import { formatRelativeTime } from "./utils";
|
||||
|
||||
interface ProjectCardProps {
|
||||
project: Project;
|
||||
|
|
@ -11,45 +13,6 @@ interface ProjectCardProps {
|
|||
onDelete: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get badge styling based on project status
|
||||
*/
|
||||
function getStatusBadgeStyles(status: ProjectStatus): string {
|
||||
switch (status) {
|
||||
case "active":
|
||||
return "tw-bg-interactive-accent-hsl/20 tw-text-accent";
|
||||
case "completed":
|
||||
return "tw-bg-success tw-text-success";
|
||||
case "archived":
|
||||
return "tw-bg-secondary tw-text-muted";
|
||||
default:
|
||||
return "tw-bg-secondary tw-text-muted";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a timestamp to relative time
|
||||
*/
|
||||
function formatRelativeTime(timestamp: number): string {
|
||||
const now = Date.now();
|
||||
const diff = now - timestamp;
|
||||
const seconds = Math.floor(diff / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
|
||||
if (days > 0) {
|
||||
return days === 1 ? "1 day ago" : `${days} days ago`;
|
||||
}
|
||||
if (hours > 0) {
|
||||
return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return minutes === 1 ? "1 minute ago" : `${minutes} minutes ago`;
|
||||
}
|
||||
return "just now";
|
||||
}
|
||||
|
||||
/**
|
||||
* ProjectCard - Individual project card with actions
|
||||
*/
|
||||
|
|
@ -86,11 +49,7 @@ export default function ProjectCard({
|
|||
{project.title}
|
||||
</h3>
|
||||
<div className="tw-flex tw-items-center tw-gap-2">
|
||||
<span
|
||||
className={`tw-rounded-sm tw-px-2 tw-py-0.5 tw-text-xs tw-font-medium tw-capitalize ${getStatusBadgeStyles(project.status)}`}
|
||||
>
|
||||
{project.status}
|
||||
</span>
|
||||
<ProjectStatusBadge status={project.status} />
|
||||
|
||||
{/* Menu button */}
|
||||
<div className="tw-relative">
|
||||
|
|
|
|||
|
|
@ -25,12 +25,8 @@ import { NoteSuggestionsDialog } from "./NoteSuggestionsDialog";
|
|||
import { ProjectStatusDialog, StatusAction } from "./ProjectStatusDialog";
|
||||
import { DiscussView } from "./discuss/DiscussView";
|
||||
import { ConversationPersistence } from "@/core/projects-plus/ConversationPersistence";
|
||||
import {
|
||||
getStatusBadgeStyles,
|
||||
formatRelativeTime,
|
||||
formatDate,
|
||||
calculateJourneySummary,
|
||||
} from "./utils";
|
||||
import { formatRelativeTime, formatDate, calculateJourneySummary } from "./utils";
|
||||
import { ProjectStatusBadge } from "./ProjectStatusBadge";
|
||||
|
||||
interface ProjectDetailProps {
|
||||
projectId: string;
|
||||
|
|
@ -220,11 +216,7 @@ export function ProjectDetail({ projectId, plugin, onBack }: ProjectDetailProps)
|
|||
<h2 className="tw-flex-1 tw-truncate tw-text-lg tw-font-semibold tw-text-normal">
|
||||
{project.title}
|
||||
</h2>
|
||||
<span
|
||||
className={`tw-shrink-0 tw-rounded-sm tw-px-2 tw-py-0.5 tw-text-xs tw-font-medium tw-capitalize ${getStatusBadgeStyles(project.status)}`}
|
||||
>
|
||||
{project.status}
|
||||
</span>
|
||||
<ProjectStatusBadge status={project.status} className="tw-shrink-0" />
|
||||
</div>
|
||||
|
||||
{/* Scrollable content */}
|
||||
|
|
|
|||
26
src/components/projects-plus/ProjectStatusBadge.tsx
Normal file
26
src/components/projects-plus/ProjectStatusBadge.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { ProjectStatus } from "@/types/projects-plus";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { getStatusBadgeStyles } from "./utils";
|
||||
import * as React from "react";
|
||||
|
||||
interface ProjectStatusBadgeProps {
|
||||
status: ProjectStatus;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable status badge component for projects
|
||||
*/
|
||||
export function ProjectStatusBadge({ status, className }: ProjectStatusBadgeProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"tw-rounded-sm tw-px-2 tw-py-0.5 tw-text-xs tw-font-medium tw-capitalize",
|
||||
getStatusBadgeStyles(status),
|
||||
className
|
||||
)}
|
||||
>
|
||||
{status}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
@ -824,7 +824,6 @@ export const DEFAULT_SETTINGS: CopilotSettings = {
|
|||
quickCommandModelKey: undefined,
|
||||
quickCommandIncludeNoteContext: true,
|
||||
autoIncludeTextSelection: false,
|
||||
projectsPlusEnabled: false,
|
||||
projectsPlusFolder: DEFAULT_PROJECTS_FOLDER,
|
||||
};
|
||||
|
||||
|
|
|
|||
10
src/main.ts
10
src/main.ts
|
|
@ -123,11 +123,8 @@ export default class CopilotPlugin extends Plugin {
|
|||
// Initialize UserMemoryManager
|
||||
this.userMemoryManager = new UserMemoryManager(this.app);
|
||||
|
||||
// Initialize ProjectManager for Projects+
|
||||
// Initialize ProjectManager for Projects+ (full initialization happens when view opens)
|
||||
this.projectsPlusManager = new ProjectManager(this.app);
|
||||
if (getSettings().projectsPlusEnabled) {
|
||||
await this.projectsPlusManager.initialize();
|
||||
}
|
||||
|
||||
// Initialize NoteAssignmentService for Projects+
|
||||
this.noteAssignmentService = new NoteAssignmentService(this.app, () => {
|
||||
|
|
@ -456,10 +453,7 @@ export default class CopilotPlugin extends Plugin {
|
|||
* Open the Projects+ panel
|
||||
*/
|
||||
async activateProjectsView(): Promise<void> {
|
||||
// Initialize ProjectManager if not already done
|
||||
if (!this.projectsPlusManager) {
|
||||
this.projectsPlusManager = new ProjectManager(this.app);
|
||||
}
|
||||
// Initialize ProjectManager before opening the view
|
||||
await this.projectsPlusManager.initialize();
|
||||
|
||||
const leaves = this.app.workspace.getLeavesOfType(PROJECTS_PLUS_VIEWTYPE);
|
||||
|
|
|
|||
|
|
@ -153,8 +153,6 @@ export interface CopilotSettings {
|
|||
quickCommandIncludeNoteContext: boolean;
|
||||
/** Automatically add text selections to chat context */
|
||||
autoIncludeTextSelection: boolean;
|
||||
/** Enable Projects+ feature */
|
||||
projectsPlusEnabled: boolean;
|
||||
/** Folder path for Projects+ data */
|
||||
projectsPlusFolder: string;
|
||||
}
|
||||
|
|
@ -428,11 +426,6 @@ export function sanitizeSettings(settings: CopilotSettings): CopilotSettings {
|
|||
|
||||
sanitizedSettings.qaExclusions = sanitizeQaExclusions(settingsToSanitize.qaExclusions);
|
||||
|
||||
// Ensure projectsPlusEnabled has a default value
|
||||
if (typeof sanitizedSettings.projectsPlusEnabled !== "boolean") {
|
||||
sanitizedSettings.projectsPlusEnabled = DEFAULT_SETTINGS.projectsPlusEnabled;
|
||||
}
|
||||
|
||||
// Ensure projectsPlusFolder has a valid value
|
||||
const projectsFolder = (settingsToSanitize.projectsPlusFolder || "").trim();
|
||||
sanitizedSettings.projectsPlusFolder =
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { SettingItem } from "@/components/ui/setting-item";
|
|||
import { updateSetting, useSettingsValue } from "@/settings/model";
|
||||
import React from "react";
|
||||
import { ToolSettingsSection } from "./ToolSettingsSection";
|
||||
import { ProjectsPlusSettings } from "./ProjectsPlusSettings";
|
||||
|
||||
export const CopilotPlusSettings: React.FC = () => {
|
||||
const settings = useSettingsValue();
|
||||
|
|
@ -79,6 +80,8 @@ export const CopilotPlusSettings: React.FC = () => {
|
|||
updateSetting("enableSavedMemory", checked);
|
||||
}}
|
||||
/>
|
||||
|
||||
<ProjectsPlusSettings />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
30
src/settings/v2/components/ProjectsPlusSettings.tsx
Normal file
30
src/settings/v2/components/ProjectsPlusSettings.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { SettingItem } from "@/components/ui/setting-item";
|
||||
import { updateSetting, useSettingsValue } from "@/settings/model";
|
||||
import React from "react";
|
||||
|
||||
/**
|
||||
* Settings section for Projects+ feature configuration
|
||||
*/
|
||||
export const ProjectsPlusSettings: React.FC = () => {
|
||||
const settings = useSettingsValue();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tw-pt-4 tw-text-xl tw-font-semibold">Projects+</div>
|
||||
|
||||
<SettingItem
|
||||
type="text"
|
||||
title="Projects Folder"
|
||||
description="Folder where project data is stored. Each project creates a subfolder here."
|
||||
value={settings.projectsPlusFolder}
|
||||
onChange={(value) => updateSetting("projectsPlusFolder", value)}
|
||||
placeholder="copilot/projects"
|
||||
/>
|
||||
|
||||
<div className="tw-rounded tw-bg-secondary tw-p-3 tw-text-sm tw-text-muted">
|
||||
<strong>Note:</strong> Projects+ uses the same inclusion/exclusion settings as QA Mode for
|
||||
note suggestions. Configure them in the <strong>QA</strong> settings tab.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in a new issue