Compare commits

...

10 Commits

Author SHA1 Message Date
Keith Solomon
c2cdf23b13 📄 docs: Add screenshot 2025-06-22 21:29:16 -05:00
Keith Solomon
52f2d39ae6 📄 docs: Update links 2025-06-22 21:17:50 -05:00
Keith Solomon
b9530abd8b 🐞 fix: Update package.json for bundling 2025-06-22 21:14:41 -05:00
Keith Solomon
f2ec8c38ac 📄 docs: Add readme 2025-06-22 21:14:22 -05:00
Keith Solomon
44ba496666 🐞 fix: Update publisher name 2025-06-22 20:45:37 -05:00
Keith Solomon
87e8c4feee feature: Make checklist file configurable 2025-06-22 20:40:47 -05:00
Keith Solomon
61ea6fab6b feature: Add webview to display the checklist in an editor tab 2025-06-22 16:48:18 -05:00
Keith Solomon
6e0405b192 feature: Fold completed phases, open first phase with incomplete tasks by default 2025-06-22 14:20:22 -05:00
Keith Solomon
0a768d1701 feature: Add completion percentage and bar to phase titles 2025-06-22 11:16:02 -05:00
Keith Solomon
21a7e972f2 feature: First working version 2025-06-22 10:51:16 -05:00
8 changed files with 606 additions and 81 deletions

2
.vscode/tasks.json vendored
View File

@@ -21,7 +21,7 @@
"type": "npm",
"script": "watch:esbuild",
"group": "build",
"problemMatcher": "$esbuild-watch",
"problemMatcher": [],
"isBackground": true,
"label": "npm: watch:esbuild",
"presentation": {

90
Development Checklist.md Normal file
View File

@@ -0,0 +1,90 @@
# ✅ Prompt Catalog Development Checklist
## 🔧 Phase 1: Planning & Setup
- [ ] Review and finalize requirements from `Prompt Catalog Features.md`
- [ ] Choose JavaScript framework (React, Vue, etc.)
- [ ] Set up Supabase project
- [ ] Create `prompts` table
- [ ] Create `users` table (future)
- [ ] Create `user_prompts` table (future)
- [ ] Define JSON structure for import/export
- [ ] Choose hosting platform (Vercel, Netlify, etc.)
---
## 🧱 Phase 2: Database & API
- [ ] Define and implement Supabase schema
- [ ] Set up Supabase RLS rules (if applicable)
- [ ] Connect frontend to Supabase using client API
---
## 🖼 Phase 3: Front-End Interface
- [ ] Build static UI from `Front End Interface.png`
- [ ] Sidebar navigation (System / Task)
- [ ] Search bar with filters
- [ ] Prompt list display
- [ ] Prompt detail view
- [ ] Tags display and interaction
- [ ] Integrate UI with Supabase for live data
- [ ] Implement CRUD operations for prompts
---
## 🔍 Phase 4: Search & Tagging
- [ ] Implement keyword and full-text search
- [ ] Add filter by:
- [ ] Type (System, Task)
- [ ] Tags (multi-select)
- [ ] Create tag suggestion/autocomplete
---
## 🤖 Phase 5: AI Integration
- [ ] Set up API key management (e.g., OpenAI, Together, Ollama)
- [ ] Add prompt suggestion UI for user input
- [ ] Integrate with AI API to return prompt suggestions
---
## 📦 Phase 6: Import/Export
- [ ] Implement prompt export to JSON
- [ ] Implement prompt import from JSON with validation
---
## 🔐 Phase 7: Authentication & User Features (Future)
- [ ] Add Supabase Auth for login/register
- [ ] Create user profile UI
- [ ] Track user-owned prompts
- [ ] Enable user favorites system
---
## 🚀 Phase 8: Deployment & QA
- [ ] Deploy frontend to hosting platform
- [ ] Set up Supabase production environment
- [ ] QA Testing:
- [ ] UI functionality
- [ ] Prompt CRUD operations
- [ ] Search and filtering
- [ ] Import/export behavior
- [ ] Write usage documentation
---
## 🌱 Phase 9: Post-MVP Enhancements
- [ ] Add prompt rating system
- [ ] Implement version history tracking
- [ ] Add social sharing (links, embed)
- [ ] Provide external API for prompt access
- [ ] Improve AI integration with context-aware suggestions

141
README.md
View File

@@ -1,71 +1,94 @@
# vscode-project-roadmap README
# VSCode Project Roadmap
This is the README for your extension "vscode-project-roadmap". After writing up a brief description, we recommend including the following sections.
## Features
Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
For example if there is an image subfolder under your extension project workspace:
\!\[feature X\]\(images/feature-x.png\)
> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
## Requirements
If you have any requirements or dependencies, add a section describing those and how to install and configure them.
## Extension Settings
Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
For example:
This extension contributes the following settings:
* `myExtension.enable`: Enable/disable this extension.
* `myExtension.thing`: Set to `blah` to do something.
## Known Issues
Calling out known issues can help limit users opening duplicate issues against your extension.
## Release Notes
Users appreciate release notes as you update your extension.
### 1.0.0
Initial release of ...
### 1.0.1
Fixed issue #.
### 1.1.0
Added features X, Y, and Z.
A Visual Studio Code extension to visualize and track your project's progress by phase using a Markdown checklist file. Perfect for teams and solo developers who want a clear, interactive roadmap directly in their editor.
---
## Following extension guidelines
## Features
Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension.
- **Roadmap Sidebar:** View your project phases and tasks as a collapsible tree in the VS Code sidebar.
- **Markdown Integration:** Uses a customizable Markdown checklist file (default: `Development Checklist.md`) for easy editing and version control.
- **Checkbox Sync:** Toggle tasks as complete/incomplete directly from the tree view—updates your Markdown file instantly.
- **Progress Indicators:** See completion bars and checkmarks for each phase at a glance.
- **Webview Overview:** Open a tab with a clean, read-only summary of your roadmap and progress.
- **Customizable Filename:** Change the checklist file via settings to fit any workflow.
* [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)
---
## Working with Markdown
## Screenshot
You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
![Roadmap Screenshot](media/roadmap-screenshot.png)
* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux).
* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux).
* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets.
---
## For more information
## Requirements
* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
- Visual Studio Code v1.101.0 or higher
**Enjoy!**
---
## Extension Settings
This extension contributes the following settings:
| Setting | Description | Default |
|-------------------------------|----------------------------------------------------------|----------------------------|
| `roadmapChecklist.filename` | The relative path to the Markdown file used for roadmap. | `Development Checklist.md` |
You can change this in your workspace settings to use a different checklist file.
---
## Usage
1. **Prepare your checklist:**
Use a Markdown file with phases as headings (`## Phase Name`) and tasks as checkboxes:
```markdown
## Phase 1: Planning
- [ ] Define requirements
- [x] Set up repository
## Phase 2: Development
- [ ] Implement feature X
- [ ] Write tests
```
2. **Open the Roadmap Sidebar:**
Click the "Roadmap" icon in the Activity Bar to view and interact with your checklist.
3. **Toggle tasks:**
Click checkboxes in the tree to mark tasks as complete/incomplete.
4. **View overview:**
Right-click a phase or use the command palette to open the roadmap overview tab.
---
## Known Issues
- Only supports checklists in Markdown files with - [ ] or - [x] syntax.
- Nested tasks are supported via indentation, but only one level deep is recommended for clarity.
---
## Release Notes
0.0.1
Initial release: Roadmap sidebar, checklist sync, overview tab, and settings.
---
## Contributing
Pull requests and suggestions are welcome! Please open an issue to discuss your ideas.
---
## Resources
- [Extension Guidelines](https://code.visualstudio.com/api/ux-guidelines/overview)
- [Markdown Syntax Reference](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github)
---
**Enjoy tracking your project roadmap in VS Code!**

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

3
media/roadmap.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
<path d="M2 2h12v12H2z"/>
</svg>

After

Width:  |  Height:  |  Size: 116 B

View File

@@ -1,7 +1,12 @@
{
"name": "vscode-project-roadmap",
"displayName": "VSCode Project Roadmap",
"publisher": "solo-web-works",
"description": "Extension to track a project by phase using a Markdown checklist file.",
"repository": {
"type": "git",
"url": "https://git.keithsolomon.net/Solo_Web_Works/VSCode_Project_Roadmap"
},
"version": "0.0.1",
"engines": {
"vscode": "^1.101.0"
@@ -9,15 +14,51 @@
"categories": [
"Other"
],
"activationEvents": [],
"activationEvents": [
"onStartupFinished",
"workspaceContains:Development Checklist.md"
],
"main": "./dist/extension.js",
"contributes": {
"commands": [
{
"command": "vscode-project-roadmap.helloWorld",
"title": "Hello World"
"command": "roadmapChecklist.openTab",
"title": "Roadmap: Open Tab"
}
]
],
"configuration": [
{
"type": "string",
"default": "Development Checklist.md",
"description": "Filename of the markdown checklist to use for the roadmap view.",
"scope": "workspace",
"properties": {
"roadmapChecklist.filename": {
"type": "string",
"default": "Development Checklist.md",
"description": "The relative path to the markdown file used for the roadmap checklist."
}
}
}
],
"viewsContainers": {
"activitybar": [
{
"id": "roadmapSidebar",
"title": "Roadmap",
"icon": "media/roadmap.svg"
}
]
},
"views": {
"roadmapSidebar": [
{
"id": "roadmapChecklist",
"name": "Checklist",
"icon": "media/roadmap.svg"
}
]
}
},
"scripts": {
"vscode:prepublish": "npm run package",

View File

@@ -1,26 +1,94 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import { RoadmapTreeProvider, RoadmapItem } from './roadmapTree';
import * as fs from 'fs';
import * as path from 'path';
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
export function activate(this: any, context: vscode.ExtensionContext) {
const output = vscode.window.createOutputChannel('Roadmap Checklist');
const config = vscode.workspace.getConfiguration('roadmapChecklist');
const checklistFilename = config.get<string>('filename') || 'Development Checklist.md';
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "vscode-project-roadmap" is now active!');
if (workspaceFolder) {
const filePath = path.join(workspaceFolder.uri.fsPath, checklistFilename);
this.checklistPath = filePath;
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
const disposable = vscode.commands.registerCommand('vscode-project-roadmap.helloWorld', () => {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World from VSCode Project Roadmap!');
console.log(`Using checklist file: ${this.checklistPath}`);
output.appendLine(`Using checklist file: ${this.checklistPath}`);
if (!fs.existsSync(this.checklistPath)) {
vscode.window.showWarningMessage(`Checklist file not found: ${this.checklistPath}`);
} else {
output.appendLine('Checklist file found, refreshing...');
setTimeout(() => this.refresh(), 0);
}
}
const configSet = `Roadmap Checklist filename set to: ${checklistFilename}`;
output.appendLine(configSet);
const checklistPath = vscode.workspace.workspaceFolders?.[0]
? vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, checklistFilename).fsPath
: '';
const message = `Using checklist file: ${this.checklistPath}`;
console.log(message);
output.appendLine(message);
output.show(); // Optional, only if you want it to be visible
// if (fs.existsSync(checklistPath)) {
// console.log('[Extension] Roadmap checklist file found:', checklistPath);
// } else {
// vscode.window.showWarningMessage(`Roadmap checklist file not found: ${checklistPath}`);
// }
const roadmapProvider = new RoadmapTreeProvider(checklistPath, context);
vscode.window.registerTreeDataProvider('roadmapChecklist', roadmapProvider);
const treeView = vscode.window.createTreeView('roadmapChecklist', {
treeDataProvider: roadmapProvider
});
context.subscriptions.push(disposable);
context.subscriptions.push(
vscode.commands.registerCommand('roadmap.toggleCheckbox', async (item: RoadmapItem) => {
const doc = vscode.workspace.textDocuments.find(d => d.uri.fsPath === checklistPath);
if (!doc) { return; }
const edit = new vscode.WorkspaceEdit();
const rawText = doc.getText();
// Normalize line endings and split into lines
const lines = rawText.split(/\r?\n/);
const index = lines.findIndex(line =>
line.trim().match(/^[-*]\s+\[[ xX]\]/) &&
line.includes(item.label)
);
if (index === -1) { return; }
const line = lines[index];
const toggledLine = line.replace(/\[(x| )\]/i, item.checked ? '[ ]' : '[x]');
lines[index] = toggledLine;
// Ensure exactly one trailing newline
const finalText = lines.join('\n').replace(/\n+$/, '') + '\n';
const fullRange = new vscode.Range(
doc.positionAt(0),
doc.positionAt(rawText.length)
);
edit.replace(doc.uri, fullRange, finalText);
await vscode.workspace.applyEdit(edit);
await doc.save(); // ✅ Triggers refresh via onDidSave
}),
vscode.commands.registerCommand('roadmapChecklist.reveal', async (item: RoadmapItem) => {
treeView.reveal(item, { expand: true });
})
);
function escapeRegex(str: string) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
}
// This method is called when your extension is deactivated
export function deactivate() {}

300
src/roadmapTree.ts Normal file
View File

@@ -0,0 +1,300 @@
// roadmapTree.ts
import * as vscode from 'vscode';
import * as fs from 'fs';
export class RoadmapItem extends vscode.TreeItem {
public children: RoadmapItem[] = [];
public readonly checked: boolean;
constructor(
public readonly label: string,
public collapsibleState: vscode.TreeItemCollapsibleState,
checked: boolean = false
) {
super(label, collapsibleState);
this.checked = checked;
if (collapsibleState === vscode.TreeItemCollapsibleState.None) {
// this is a task
this.description = checked ? '✅ Done' : '';
this.iconPath = new vscode.ThemeIcon(checked ? 'check' : 'circle-large-outline');
this.command = {
command: 'roadmap.toggleCheckbox',
title: 'Toggle Task',
arguments: [this]
};
} else {
// this is a phase
this.command = {
command: 'roadmapChecklist.openTab',
title: 'Open Tab View',
arguments: [this]
};
}
}
updatePhaseInfo() {
if (this.children.length === 0) {return;}
const total = this.children.length;
const completed = this.children.filter(c => c.checked).length;
const percent = Math.round((completed / total) * 100);
const bar = '▓'.repeat(Math.floor(percent / 10)).padEnd(10, '░');
this.description = `${bar} ${percent}%`;
this.iconPath = new vscode.ThemeIcon(
completed === total ? 'check' : 'tasklist'
);
}
static fromMarkdownLine(
label: string,
indent: number,
checked: boolean,
lines: string[],
currentIndex: number
): RoadmapItem {
const collapsibleState = hasChildrenLater(lines, currentIndex, indent)
? vscode.TreeItemCollapsibleState.Collapsed
: vscode.TreeItemCollapsibleState.None;
return new RoadmapItem(label, collapsibleState, checked);
}
}
export class RoadmapTreeProvider implements vscode.TreeDataProvider<RoadmapItem> {
private output = vscode.window.createOutputChannel('Roadmap Checklist');
private _onDidChangeTreeData: vscode.EventEmitter<RoadmapItem | undefined | void> = new vscode.EventEmitter();
readonly onDidChangeTreeData: vscode.Event<RoadmapItem | undefined | void> = this._onDidChangeTreeData.event;
private items: RoadmapItem[] = [];
private panel: vscode.WebviewPanel | null = null;
constructor(
private readonly checklistPath: string,
private readonly context: vscode.ExtensionContext
) {
this.refresh();
vscode.workspace.onDidSaveTextDocument(doc => {
if (doc.uri.fsPath === checklistPath) {
this.refresh();
}
});
vscode.commands.registerCommand('roadmapChecklist.openTab', (item?: RoadmapItem) => {
this.showWebview();
});
}
refresh(): void {
let content = '';
const doc = vscode.workspace.textDocuments.find(d => d.uri.fsPath === this.checklistPath);
if (doc) {
content = doc.getText();
} else {
try {
content = fs.readFileSync(this.checklistPath, 'utf8');
} catch (err) {
vscode.window.showErrorMessage(`Failed to read checklist file: ${err}`);
return;
}
}
const expanded = this.context.workspaceState.get<string[]>('expandedPhases') || [];
const { items, firstOpenPhase } = this.parseMarkdown(content, expanded);
this.items = items;
this._onDidChangeTreeData.fire();
if (firstOpenPhase) {
setTimeout(() => {
vscode.commands.executeCommand('roadmapChecklist.reveal', firstOpenPhase, { expand: true });
}, 100);
}
if (this.panel) {
this.renderWebview();
}
}
getTreeItem(element: RoadmapItem): vscode.TreeItem {
return element;
}
getChildren(element?: RoadmapItem): vscode.ProviderResult<RoadmapItem[]> {
if (!element) {return this.items;}
// Track expand/collapse state
const expanded = this.context.workspaceState.get<string[]>('expandedPhases') || [];
if (element.collapsibleState === vscode.TreeItemCollapsibleState.Expanded) {
if (!expanded.includes(element.label)) {
expanded.push(element.label);
this.context.workspaceState.update('expandedPhases', expanded);
}
} else if (element.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed) {
if (expanded.includes(element.label)) {
const updated = expanded.filter(label => label !== element.label);
this.context.workspaceState.update('expandedPhases', updated);
}
}
return element.children;
}
getParent(element: RoadmapItem): vscode.ProviderResult<RoadmapItem> {
for (const phase of this.items) {
for (const task of phase.children) {
if (task === element) {return phase;}
const match = findParentRecursive(task, element);
if (match) {return match;}
}
}
return null;
}
private parseMarkdown(content: string, expandedLabels: string[]): { items: RoadmapItem[]; firstOpenPhase: RoadmapItem | null } {
const lines = content.split('\n');
const items: RoadmapItem[] = [];
let currentPhase: RoadmapItem | null = null;
const taskStack: { indent: number; item: RoadmapItem }[] = [];
let firstOpenPhase: RoadmapItem | null = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const phaseMatch = line.match(/^##\s+(.+)/);
const taskMatch = line.match(/^(\s*)[-*]\s+\[( |x)\]\s+(.+)/);
if (phaseMatch) {
const label = phaseMatch[1].trim();
const isExpanded = expandedLabels.includes(label);
const state = isExpanded ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed;
currentPhase = new RoadmapItem(label, state);
items.push(currentPhase);
taskStack.length = 0;
} else if (taskMatch && currentPhase) {
const indent = taskMatch[1].length;
const checked = taskMatch[2] === 'x';
const label = taskMatch[3].trim();
const task = RoadmapItem.fromMarkdownLine(label, indent, checked, lines, i);
while (taskStack.length && taskStack[taskStack.length - 1].indent >= indent) {
taskStack.pop();
}
if (taskStack.length === 0) {
currentPhase.children.push(task);
} else {
taskStack[taskStack.length - 1].item.children.push(task);
}
taskStack.push({ indent, item: task });
}
}
for (const phase of items) {
phase.updatePhaseInfo();
updateParentCheckState(phase);
const total = phase.children.length;
const completed = phase.children.filter(c => c.checked).length;
if (!firstOpenPhase && total > 0 && completed < total) {
firstOpenPhase = phase;
}
}
return { items, firstOpenPhase };
}
private showWebview() {
if (this.panel) {
this.panel.reveal(vscode.ViewColumn.One);
return;
}
this.panel = vscode.window.createWebviewPanel(
'roadmapTab',
'Roadmap Overview',
vscode.ViewColumn.One,
{ enableScripts: true }
);
this.renderWebview();
this.panel.onDidDispose(() => {
this.panel = null;
});
}
private renderTasks(items: RoadmapItem[]): string {
if (!items.length) {return '';}
return `<ul>` + items.map(item => {
const icon = item.checked ? '✅' : '⬜';
return `<li>${icon} ${item.label}${this.renderTasks(item.children)}</li>`;
}).join('') + `</ul>`;
}
private buildWebviewHtml(items: RoadmapItem[], webview: vscode.Webview): string {
const rows = items.map(phase => {
const taskList = this.renderTasks(phase.children);
return `
<h2>${phase.label}</h2>
<p>${phase.description}</p>
<ul>${taskList}</ul>
`;
}).join('');
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource} 'unsafe-inline'; script-src ${webview.cspSource};">
<style>
body { font-family: sans-serif; padding: 1em; }
h2 { margin-top: 1em; }
ul { padding-left: 1em; list-style-type: none; }
li { list-style-type: none; }
</style>
</head>
<body>
${rows}
</body>
</html>
`;
}
private renderWebview() {
if (this.panel) {
this.panel.webview.html = this.buildWebviewHtml(this.items, this.panel.webview);
}
}
}
function findParentRecursive(parent: RoadmapItem, child: RoadmapItem): RoadmapItem | null {
for (const c of parent.children) {
if (c === child) {return parent;}
const found = findParentRecursive(c, child);
if (found) {return found;}
}
return null;
}
function updateParentCheckState(item: RoadmapItem): boolean {
if (item.children.length === 0) {return item.checked;}
const allChildrenChecked = item.children.map(updateParentCheckState).every(Boolean);
(item as any).checked = allChildrenChecked; // hacky override to set read-only property
item.updatePhaseInfo();
return allChildrenChecked;
}
function hasChildrenLater(lines: string[], currentIndex: number, parentIndent: number): boolean {
for (let i = currentIndex + 1; i < lines.length; i++) {
const match = lines[i].match(/^(\s*)[-*]\s+\[( |x)\]/);
if (!match) {continue;}
const indent = match[1].length;
if (indent <= parentIndent) {return false;}
return true;
}
return false;
}