newpr 0.1.3 → 0.2.0

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.
Files changed (33) hide show
  1. package/package.json +11 -1
  2. package/src/analyzer/pipeline.ts +22 -5
  3. package/src/github/fetch-pr.ts +43 -1
  4. package/src/history/store.ts +106 -1
  5. package/src/llm/client.ts +197 -0
  6. package/src/llm/prompts.ts +33 -8
  7. package/src/tui/Shell.tsx +7 -2
  8. package/src/types/github.ts +11 -0
  9. package/src/types/output.ts +44 -0
  10. package/src/web/client/App.tsx +29 -3
  11. package/src/web/client/components/AppShell.tsx +94 -47
  12. package/src/web/client/components/ChatSection.tsx +427 -0
  13. package/src/web/client/components/DetailPane.tsx +163 -75
  14. package/src/web/client/components/DiffViewer.tsx +679 -0
  15. package/src/web/client/components/InputScreen.tsx +110 -26
  16. package/src/web/client/components/Markdown.tsx +169 -43
  17. package/src/web/client/components/ResultsScreen.tsx +66 -71
  18. package/src/web/client/components/TipTapEditor.tsx +405 -0
  19. package/src/web/client/hooks/useAnalysis.ts +8 -1
  20. package/src/web/client/lib/shiki.ts +63 -0
  21. package/src/web/client/panels/CartoonPanel.tsx +94 -37
  22. package/src/web/client/panels/DiscussionPanel.tsx +158 -0
  23. package/src/web/client/panels/FilesPanel.tsx +435 -54
  24. package/src/web/client/panels/GroupsPanel.tsx +49 -40
  25. package/src/web/client/panels/StoryPanel.tsx +42 -22
  26. package/src/web/components/ui/tabs.tsx +3 -3
  27. package/src/web/server/routes.ts +716 -14
  28. package/src/web/server/session-manager.ts +11 -2
  29. package/src/web/server.ts +33 -0
  30. package/src/web/styles/built.css +1 -1
  31. package/src/web/styles/globals.css +117 -1
  32. package/src/web/client/panels/NarrativePanel.tsx +0 -9
  33. package/src/web/client/panels/SummaryPanel.tsx +0 -20
@@ -1,15 +1,15 @@
1
1
  import { useState } from "react";
2
- import { ChevronDown, ChevronRight } from "lucide-react";
2
+ import { ChevronRight } from "lucide-react";
3
3
  import type { FileGroup } from "../../../types/output.ts";
4
4
 
5
- const TYPE_COLORS: Record<string, string> = {
6
- feature: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
7
- refactor: "bg-purple-500/10 text-purple-600 dark:text-purple-400",
8
- bugfix: "bg-red-500/10 text-red-600 dark:text-red-400",
9
- chore: "bg-gray-500/10 text-gray-600 dark:text-gray-400",
10
- docs: "bg-teal-500/10 text-teal-600 dark:text-teal-400",
11
- test: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
12
- config: "bg-orange-500/10 text-orange-600 dark:text-orange-400",
5
+ const TYPE_DOT: Record<string, string> = {
6
+ feature: "bg-blue-500",
7
+ refactor: "bg-purple-500",
8
+ bugfix: "bg-red-500",
9
+ chore: "bg-neutral-400",
10
+ docs: "bg-teal-500",
11
+ test: "bg-yellow-500",
12
+ config: "bg-orange-500",
13
13
  };
14
14
 
15
15
  export function GroupsPanel({ groups }: { groups: FileGroup[] }) {
@@ -24,39 +24,48 @@ export function GroupsPanel({ groups }: { groups: FileGroup[] }) {
24
24
  }
25
25
 
26
26
  return (
27
- <div className="pt-6 divide-y">
28
- {groups.map((group, i) => (
29
- <div key={group.name}>
30
- <button
31
- type="button"
32
- className="w-full flex items-center gap-3 min-w-0 py-4 text-left hover:bg-accent/30 transition-colors -mx-2 px-2 rounded-md"
33
- onClick={() => toggle(i)}
34
- >
35
- {expanded.has(i) ? (
36
- <ChevronDown className="h-4 w-4 text-muted-foreground shrink-0" />
37
- ) : (
38
- <ChevronRight className="h-4 w-4 text-muted-foreground shrink-0" />
39
- )}
40
- <span className="text-sm font-medium flex-1 min-w-0 truncate">{group.name}</span>
41
- <span className={`text-xs font-medium px-2 py-0.5 rounded-full shrink-0 ${TYPE_COLORS[group.type] ?? TYPE_COLORS.chore}`}>
42
- {group.type}
43
- </span>
44
- <span className="text-xs text-muted-foreground shrink-0">{group.files.length} files</span>
45
- </button>
46
- {expanded.has(i) && (
47
- <div className="pb-4 pl-8">
48
- <p className="text-sm text-muted-foreground mb-3 break-words">{group.description}</p>
49
- <div className="space-y-1">
50
- {group.files.map((f) => (
51
- <div key={f} className="text-xs font-mono text-muted-foreground pl-2 border-l-2 border-border truncate" title={f}>
52
- {f}
27
+ <div className="pt-5">
28
+ <div className="text-[10px] font-medium text-muted-foreground/40 uppercase tracking-wider mb-3">
29
+ {groups.length} groups
30
+ </div>
31
+ <div className="space-y-px">
32
+ {groups.map((group, i) => {
33
+ const isOpen = expanded.has(i);
34
+ return (
35
+ <div key={group.name}>
36
+ <button
37
+ type="button"
38
+ className={`w-full flex items-center gap-2.5 py-2.5 px-2.5 -mx-1 text-left rounded-lg transition-colors ${
39
+ isOpen ? "bg-accent/50" : "hover:bg-accent/30"
40
+ }`}
41
+ onClick={() => toggle(i)}
42
+ >
43
+ <ChevronRight className={`h-3 w-3 text-muted-foreground/40 shrink-0 transition-transform ${isOpen ? "rotate-90" : ""}`} />
44
+ <span className={`h-1.5 w-1.5 rounded-full shrink-0 ${TYPE_DOT[group.type] ?? TYPE_DOT.chore}`} />
45
+ <span className="text-xs font-medium flex-1 min-w-0 truncate">{group.name}</span>
46
+ <span className="text-[10px] text-muted-foreground/30 shrink-0">{group.type}</span>
47
+ <span className="text-[10px] text-muted-foreground/30 shrink-0 tabular-nums">{group.files.length}</span>
48
+ </button>
49
+ {isOpen && (
50
+ <div className="pl-[34px] pr-2 pb-3 pt-1">
51
+ <p className="text-[11px] text-muted-foreground/60 leading-relaxed mb-2.5">{group.description}</p>
52
+ <div className="space-y-0.5">
53
+ {group.files.map((f) => (
54
+ <div
55
+ key={f}
56
+ className="text-[11px] font-mono text-muted-foreground/50 truncate py-0.5"
57
+ title={f}
58
+ >
59
+ {f}
60
+ </div>
61
+ ))}
53
62
  </div>
54
- ))}
55
- </div>
63
+ </div>
64
+ )}
56
65
  </div>
57
- )}
58
- </div>
59
- ))}
66
+ );
67
+ })}
68
+ </div>
60
69
  </div>
61
70
  );
62
71
  }
@@ -1,5 +1,16 @@
1
1
  import type { NewprOutput } from "../../../types/output.ts";
2
2
  import { Markdown } from "../components/Markdown.tsx";
3
+ import { ChatMessages } from "../components/ChatSection.tsx";
4
+
5
+ const TYPE_DOT: Record<string, string> = {
6
+ feature: "bg-blue-500",
7
+ refactor: "bg-purple-500",
8
+ bugfix: "bg-red-500",
9
+ chore: "bg-neutral-400",
10
+ docs: "bg-teal-500",
11
+ test: "bg-yellow-500",
12
+ config: "bg-orange-500",
13
+ };
3
14
 
4
15
  export function StoryPanel({
5
16
  data,
@@ -13,42 +24,51 @@ export function StoryPanel({
13
24
  const { summary, groups, narrative } = data;
14
25
 
15
26
  return (
16
- <div className="pt-4 space-y-5">
17
- <div className="space-y-3">
18
- <p className="text-xs text-muted-foreground leading-relaxed">{summary.purpose}</p>
19
- <div className="grid grid-cols-2 gap-4">
27
+ <div className="pt-5 space-y-6">
28
+ <div className="space-y-4">
29
+ <p className="text-xs text-foreground/80 leading-relaxed">{summary.purpose}</p>
30
+
31
+ <div className="grid grid-cols-2 gap-x-6 gap-y-3">
20
32
  <div>
21
- <span className="text-[10px] font-medium text-muted-foreground/60 uppercase tracking-wider">Scope</span>
22
- <p className="text-xs text-muted-foreground mt-0.5">{summary.scope}</p>
33
+ <div className="text-[10px] font-medium text-muted-foreground/40 uppercase tracking-wider mb-1">Scope</div>
34
+ <p className="text-[11px] text-muted-foreground/70 leading-relaxed">{summary.scope}</p>
23
35
  </div>
24
36
  <div>
25
- <span className="text-[10px] font-medium text-muted-foreground/60 uppercase tracking-wider">Impact</span>
26
- <p className="text-xs text-muted-foreground mt-0.5">{summary.impact}</p>
37
+ <div className="text-[10px] font-medium text-muted-foreground/40 uppercase tracking-wider mb-1">Impact</div>
38
+ <p className="text-[11px] text-muted-foreground/70 leading-relaxed">{summary.impact}</p>
27
39
  </div>
28
40
  </div>
41
+
29
42
  <div className="flex flex-wrap gap-1.5">
30
- {groups.map((g) => (
31
- <button
32
- key={g.name}
33
- type="button"
34
- onClick={() => onAnchorClick("group", g.name)}
35
- className={`text-[11px] px-2 py-0.5 rounded-full font-medium transition-colors ${
36
- activeId === `group:${g.name}`
37
- ? "bg-blue-500/20 text-blue-500 dark:text-blue-300 ring-1 ring-blue-500/40"
38
- : "bg-muted hover:bg-muted/80"
39
- }`}
40
- >
41
- {g.name}
42
- </button>
43
- ))}
43
+ {groups.map((g) => {
44
+ const isActive = activeId === `group:${g.name}`;
45
+ return (
46
+ <button
47
+ key={g.name}
48
+ type="button"
49
+ onClick={() => onAnchorClick("group", g.name)}
50
+ className={`inline-flex items-center gap-1.5 text-[11px] px-2 py-0.5 rounded-md transition-colors ${
51
+ isActive
52
+ ? "bg-accent text-foreground font-medium"
53
+ : "text-muted-foreground/60 hover:text-foreground hover:bg-accent/40"
54
+ }`}
55
+ >
56
+ <span className={`h-1.5 w-1.5 rounded-full shrink-0 ${TYPE_DOT[g.type] ?? TYPE_DOT.chore}`} />
57
+ {g.name}
58
+ </button>
59
+ );
60
+ })}
44
61
  </div>
45
62
  </div>
46
63
 
47
64
  <div className="border-t pt-5">
65
+ <div className="text-[10px] font-medium text-muted-foreground/40 uppercase tracking-wider mb-4">Walkthrough</div>
48
66
  <Markdown onAnchorClick={onAnchorClick} activeId={activeId}>
49
67
  {narrative}
50
68
  </Markdown>
51
69
  </div>
70
+
71
+ <ChatMessages onAnchorClick={onAnchorClick} activeId={activeId} />
52
72
  </div>
53
73
  );
54
74
  }
@@ -11,7 +11,7 @@ const TabsList = React.forwardRef<
11
11
  <TabsPrimitive.List
12
12
  ref={ref}
13
13
  className={cn(
14
- "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
14
+ "inline-flex items-center gap-0 border-b border-border -mb-px",
15
15
  className,
16
16
  )}
17
17
  {...props}
@@ -26,7 +26,7 @@ const TabsTrigger = React.forwardRef<
26
26
  <TabsPrimitive.Trigger
27
27
  ref={ref}
28
28
  className={cn(
29
- "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
29
+ "inline-flex items-center justify-center gap-1.5 whitespace-nowrap px-3 pb-2.5 pt-1 text-xs font-medium text-muted-foreground/60 transition-colors hover:text-foreground/80 border-b-2 border-transparent -mb-px data-[state=active]:text-foreground data-[state=active]:border-foreground focus-visible:outline-none",
30
30
  className,
31
31
  )}
32
32
  {...props}
@@ -41,7 +41,7 @@ const TabsContent = React.forwardRef<
41
41
  <TabsPrimitive.Content
42
42
  ref={ref}
43
43
  className={cn(
44
- "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
44
+ "mt-0 focus-visible:outline-none",
45
45
  className,
46
46
  )}
47
47
  {...props}