@dreki-gg/pi-plan-mode 0.14.2 → 0.14.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @dreki-gg/pi-plan-mode
2
2
 
3
+ ## 0.14.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Proper markdown rendering in plan.html — fenced code blocks, inline code, bold, italic, links, and all heading levels.
8
+
9
+ ## 0.14.4
10
+
11
+ ### Patch Changes
12
+
13
+ - Remove task list widget entirely — plan.jsonl is the source of truth.
14
+
15
+ ## 0.14.3
16
+
17
+ ### Patch Changes
18
+
19
+ - Only show task widget during active plan execution, not after exiting plan mode.
20
+
3
21
  ## 0.14.2
4
22
 
5
23
  ### Patch Changes
@@ -24,39 +24,83 @@ function markdownToHtml(markdown: string): string {
24
24
  const lines = markdown.split(/\r?\n/);
25
25
  const html: string[] = [];
26
26
  let inList = false;
27
+ let inCode = false;
28
+ let codeLang = '';
29
+ const codeLines: string[] = [];
27
30
 
28
31
  for (const line of lines) {
29
- if (line.startsWith('# ')) {
30
- if (inList) {
31
- html.push('</ul>');
32
- inList = false;
32
+ // Fenced code blocks
33
+ if (line.startsWith('```')) {
34
+ if (!inCode) {
35
+ if (inList) { html.push('</ul>'); inList = false; }
36
+ inCode = true;
37
+ codeLang = line.slice(3).trim();
38
+ codeLines.length = 0;
39
+ } else {
40
+ const langAttr = codeLang ? ` class="language-${escapeHtml(codeLang)}"` : '';
41
+ html.push(`<pre><code${langAttr}>${codeLines.map(escapeHtml).join('\n')}</code></pre>`);
42
+ inCode = false;
43
+ codeLang = '';
33
44
  }
34
- html.push(`<h1>${escapeHtml(line.slice(2))}</h1>`);
35
- } else if (line.startsWith('## ')) {
36
- if (inList) {
37
- html.push('</ul>');
38
- inList = false;
39
- }
40
- html.push(`<h2>${escapeHtml(line.slice(3))}</h2>`);
41
- } else if (line.startsWith('- ')) {
42
- if (!inList) {
43
- html.push('<ul>');
44
- inList = true;
45
- }
46
- html.push(`<li>${escapeHtml(line.slice(2))}</li>`);
47
- } else if (line.trim()) {
48
- if (inList) {
49
- html.push('</ul>');
50
- inList = false;
51
- }
52
- html.push(`<p>${escapeHtml(line)}</p>`);
45
+ continue;
46
+ }
47
+
48
+ if (inCode) {
49
+ codeLines.push(line);
50
+ continue;
51
+ }
52
+
53
+ // Headings
54
+ const headingMatch = line.match(/^(#{1,6})\s+(.*)$/);
55
+ if (headingMatch) {
56
+ if (inList) { html.push('</ul>'); inList = false; }
57
+ const level = headingMatch[1].length;
58
+ html.push(`<h${level}>${inlineMarkdown(escapeHtml(headingMatch[2]))}</h${level}>`);
59
+ continue;
53
60
  }
61
+
62
+ // List items (- or *)
63
+ const listMatch = line.match(/^\s*[-*]\s+(.*)$/);
64
+ if (listMatch) {
65
+ if (!inList) { html.push('<ul>'); inList = true; }
66
+ html.push(`<li>${inlineMarkdown(escapeHtml(listMatch[1]))}</li>`);
67
+ continue;
68
+ }
69
+
70
+ // Blank line
71
+ if (!line.trim()) {
72
+ if (inList) { html.push('</ul>'); inList = false; }
73
+ continue;
74
+ }
75
+
76
+ // Paragraph
77
+ if (inList) { html.push('</ul>'); inList = false; }
78
+ html.push(`<p>${inlineMarkdown(escapeHtml(line))}</p>`);
54
79
  }
55
80
 
81
+ // Close unclosed blocks
82
+ if (inCode) {
83
+ html.push(`<pre><code>${codeLines.map(escapeHtml).join('\n')}</code></pre>`);
84
+ }
56
85
  if (inList) html.push('</ul>');
57
86
  return html.join('\n');
58
87
  }
59
88
 
89
+ /** Converts inline markdown (bold, italic, inline code, links) in already-escaped HTML. */
90
+ function inlineMarkdown(text: string): string {
91
+ return text
92
+ // Inline code: `code`
93
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
94
+ // Bold: **text** or __text__
95
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
96
+ .replace(/__(.+?)__/g, '<strong>$1</strong>')
97
+ // Italic: *text* or _text_
98
+ .replace(/\*(.+?)\*/g, '<em>$1</em>')
99
+ .replace(/_(.+?)_/g, '<em>$1</em>')
100
+ // Links: [text](url)
101
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
102
+ }
103
+
60
104
  function escapeHtml(value: string): string {
61
105
  return value
62
106
  .replaceAll('&', '&amp;')
@@ -17,6 +17,16 @@ html(lang="en")
17
17
  section { background:rgba(17,20,27,0.82); border:1px solid var(--line); border-radius:18px; padding:22px; margin:18px 0; box-shadow:0 24px 80px rgba(0,0,0,0.24); }
18
18
  .handoff :first-child { margin-top:0; }
19
19
  .handoff :last-child { margin-bottom:0; }
20
+ .handoff pre { background:#161922; border:1px solid var(--line); border-radius:10px; padding:14px 16px; overflow-x:auto; margin:12px 0; }
21
+ .handoff pre code { font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:13px; color:#c7ccda; background:none; padding:0; border:none; border-radius:0; }
22
+ .handoff code { font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:13px; background:rgba(139,92,246,0.12); color:var(--accent); padding:2px 6px; border-radius:5px; }
23
+ .handoff strong { font-weight:650; }
24
+ .handoff a { color:var(--accent); text-decoration:none; }
25
+ .handoff a:hover { text-decoration:underline; }
26
+ .handoff h1, .handoff h3, .handoff h4 { font-size:15px; text-transform:uppercase; color:var(--muted); letter-spacing:0.14em; margin:20px 0 10px; }
27
+ .handoff p { margin:8px 0; }
28
+ .handoff ul { margin:8px 0; padding-left:20px; }
29
+ .handoff li { margin:4px 0; }
20
30
  .tasks { display:grid; gap:12px; }
21
31
  .task { border:1px solid var(--line); border-radius:14px; padding:16px; background:#0f1218; }
22
32
  .task-top { display:flex; gap:10px; align-items:center; margin-bottom:8px; }
@@ -22,25 +22,6 @@ export function updateUI(state: PlanModeState, ctx: ExtensionContext): void {
22
22
  ctx.ui.setStatus('plan-mode', undefined);
23
23
  }
24
24
 
25
- const allResolved =
26
- state.plan?.tasks.every((t) => t.status === 'done' || t.status === 'skipped' || t.status === 'blocked') ?? false;
27
-
28
- if ((state.executing || state.plan) && state.plan && !allResolved) {
29
- const lines = state.plan.tasks.map((task) => {
30
- const label = `${task.id} ${task.description}`;
31
- switch (task.status) {
32
- case 'done':
33
- return theme.fg('success', '✓ ') + theme.fg('muted', theme.strikethrough(label));
34
- case 'skipped':
35
- return theme.fg('warning', '⊘ ') + theme.fg('muted', theme.strikethrough(label));
36
- case 'blocked':
37
- return theme.fg('error', '✗ ') + theme.fg('error', label);
38
- default:
39
- return theme.fg('muted', '☐ ') + label;
40
- }
41
- });
42
- ctx.ui.setWidget('plan-todos', lines);
43
- } else {
44
- ctx.ui.setWidget('plan-todos', undefined);
45
- }
25
+ // Task list lives in plan.jsonl — no widget needed.
26
+ ctx.ui.setWidget('plan-todos', undefined);
46
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreki-gg/pi-plan-mode",
3
- "version": "0.14.2",
3
+ "version": "0.14.5",
4
4
  "description": "Two-phase planning workflow for pi — plan with claude-opus-4-6:medium, execute with gpt-5.5:low, with .plans/ file-based handoff",
5
5
  "keywords": [
6
6
  "pi-package"