@miketromba/issy-app 0.1.0 → 0.1.1

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.
@@ -1,59 +1,79 @@
1
- import { marked, Renderer } from "marked";
2
- import hljs from "highlight.js";
3
- import "highlight.js/styles/github-dark.css";
4
- import type { Issue } from "../App";
5
- import { Badge } from "./Badge";
6
- import { formatDisplayDate, formatFullDate } from "@miketromba/issy-core";
1
+ import hljs from 'highlight.js'
2
+ import { marked, Renderer } from 'marked'
3
+ import 'highlight.js/styles/github-dark.css'
4
+ import { formatDisplayDate, formatFullDate } from '@miketromba/issy-core'
5
+ import type { Issue } from '../App'
6
+ import { Badge } from './Badge'
7
7
 
8
8
  interface IssueDetailProps {
9
- issue: Issue;
10
- onBack?: () => void;
11
- onEdit?: () => void;
12
- onClose?: () => void;
13
- onReopen?: () => void;
14
- onDelete?: () => void;
9
+ issue: Issue
10
+ onBack?: () => void
11
+ onEdit?: () => void
12
+ onClose?: () => void
13
+ onReopen?: () => void
14
+ onDelete?: () => void
15
15
  }
16
16
 
17
17
  // Custom renderer with syntax highlighting
18
- const renderer = new Renderer();
19
- renderer.code = function({ text, lang }) {
20
- const language = lang && hljs.getLanguage(lang) ? lang : "plaintext";
21
- const highlighted = hljs.highlight(text, { language }).value;
22
- return `<pre><code class="hljs language-${language}">${highlighted}</code></pre>`;
23
- };
18
+ const renderer = new Renderer()
19
+ renderer.code = ({ text, lang }) => {
20
+ const language = lang && hljs.getLanguage(lang) ? lang : 'plaintext'
21
+ const highlighted = hljs.highlight(text, { language }).value
22
+ return `<pre><code class="hljs language-${language}">${highlighted}</code></pre>`
23
+ }
24
24
 
25
25
  marked.setOptions({
26
26
  gfm: true,
27
27
  breaks: true,
28
28
  renderer,
29
- });
29
+ })
30
30
 
31
- export function IssueDetail({ issue, onBack, onEdit, onClose, onReopen, onDelete }: IssueDetailProps) {
32
- const labels = issue.frontmatter.labels?.split(',').map(l => l.trim()).filter(Boolean) || [];
33
- const isOpen = issue.frontmatter.status === 'open';
31
+ export function IssueDetail({
32
+ issue,
33
+ onBack,
34
+ onEdit,
35
+ onClose,
36
+ onReopen,
37
+ onDelete,
38
+ }: IssueDetailProps) {
39
+ const labels =
40
+ issue.frontmatter.labels
41
+ ?.split(',')
42
+ .map((l) => l.trim())
43
+ .filter(Boolean) || []
44
+ const isOpen = issue.frontmatter.status === 'open'
34
45
 
35
46
  return (
36
47
  <div className="max-w-[800px] mx-auto px-4 md:px-10 py-6 md:py-8">
37
48
  {/* Mobile back button */}
38
49
  {onBack && (
39
- <button
50
+ <button
40
51
  onClick={onBack}
41
52
  className="md:hidden inline-flex items-center gap-1.5 mb-4 text-text-secondary text-sm"
42
53
  >
43
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
54
+ <svg
55
+ width="16"
56
+ height="16"
57
+ viewBox="0 0 24 24"
58
+ fill="none"
59
+ stroke="currentColor"
60
+ strokeWidth="2"
61
+ >
44
62
  <path d="M15 19l-7-7 7-7" />
45
63
  </svg>
46
64
  Back to issues
47
65
  </button>
48
66
  )}
49
-
67
+
50
68
  <div className="mb-6">
51
69
  <div className="flex items-start justify-between gap-3 mb-3">
52
70
  <h1 className="text-xl md:text-2xl font-semibold text-text-primary leading-tight">
53
- {issue.frontmatter.title || "Untitled Issue"}
54
- <span className="font-normal text-text-muted ml-2">#{issue.id}</span>
71
+ {issue.frontmatter.title || 'Untitled Issue'}
72
+ <span className="font-normal text-text-muted ml-2">
73
+ #{issue.id}
74
+ </span>
55
75
  </h1>
56
-
76
+
57
77
  {/* Action buttons */}
58
78
  <div className="flex items-center gap-1 shrink-0">
59
79
  {onEdit && (
@@ -62,25 +82,39 @@ export function IssueDetail({ issue, onBack, onEdit, onClose, onReopen, onDelete
62
82
  title="Edit issue"
63
83
  className="p-2 text-text-muted hover:text-text-primary hover:bg-surface rounded-lg transition-colors"
64
84
  >
65
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
85
+ <svg
86
+ width="16"
87
+ height="16"
88
+ viewBox="0 0 24 24"
89
+ fill="none"
90
+ stroke="currentColor"
91
+ strokeWidth="2"
92
+ >
66
93
  <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
67
94
  <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
68
95
  </svg>
69
96
  </button>
70
97
  )}
71
-
98
+
72
99
  {onDelete && (
73
100
  <button
74
101
  onClick={onDelete}
75
102
  title="Delete issue"
76
103
  className="p-2 text-text-muted hover:text-red-400 hover:bg-red-500/10 rounded-lg transition-colors"
77
104
  >
78
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
105
+ <svg
106
+ width="16"
107
+ height="16"
108
+ viewBox="0 0 24 24"
109
+ fill="none"
110
+ stroke="currentColor"
111
+ strokeWidth="2"
112
+ >
79
113
  <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
80
114
  </svg>
81
115
  </button>
82
116
  )}
83
-
117
+
84
118
  {isOpen && onClose && (
85
119
  <button
86
120
  onClick={onClose}
@@ -90,7 +124,7 @@ export function IssueDetail({ issue, onBack, onEdit, onClose, onReopen, onDelete
90
124
  Close
91
125
  </button>
92
126
  )}
93
-
127
+
94
128
  {!isOpen && onReopen && (
95
129
  <button
96
130
  onClick={onReopen}
@@ -102,32 +136,32 @@ export function IssueDetail({ issue, onBack, onEdit, onClose, onReopen, onDelete
102
136
  )}
103
137
  </div>
104
138
  </div>
105
-
139
+
106
140
  {issue.frontmatter.description && (
107
141
  <p className="text-[15px] text-text-secondary leading-relaxed mb-4">
108
142
  {issue.frontmatter.description}
109
143
  </p>
110
144
  )}
111
-
145
+
112
146
  <div className="flex flex-wrap items-center gap-2 text-sm">
113
147
  {issue.frontmatter.priority && (
114
148
  <Badge variant="priority" value={issue.frontmatter.priority} />
115
149
  )}
116
-
150
+
117
151
  {issue.frontmatter.status && (
118
152
  <Badge variant="status" value={issue.frontmatter.status} />
119
153
  )}
120
-
154
+
121
155
  {issue.frontmatter.type && (
122
156
  <Badge variant="type" value={issue.frontmatter.type} />
123
157
  )}
124
-
158
+
125
159
  {labels.map((label) => (
126
160
  <Badge key={label} variant="label" value={label} />
127
161
  ))}
128
-
162
+
129
163
  {issue.frontmatter.created && (
130
- <span
164
+ <span
131
165
  className="text-xs text-text-muted"
132
166
  title={formatFullDate(issue.frontmatter.created)}
133
167
  >
@@ -136,13 +170,15 @@ export function IssueDetail({ issue, onBack, onEdit, onClose, onReopen, onDelete
136
170
  )}
137
171
  </div>
138
172
  </div>
139
-
173
+
140
174
  <hr className="border-0 border-t border-border my-6" />
141
-
142
- <div
175
+
176
+ <div
143
177
  className="prose prose-invert prose-sm max-w-none prose-a:text-accent prose-pre:border-0 prose-pre:shadow-none prose-code:bg-surface-elevated prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none"
144
- dangerouslySetInnerHTML={{ __html: marked.parse(issue.content) as string }}
178
+ dangerouslySetInnerHTML={{
179
+ __html: marked.parse(issue.content) as string,
180
+ }}
145
181
  />
146
182
  </div>
147
- );
183
+ )
148
184
  }
@@ -1,65 +1,63 @@
1
- import type { Issue } from "../App";
2
- import { Badge } from "./Badge";
3
- import { formatDisplayDate, formatFullDate } from "@miketromba/issy-core";
1
+ import { formatDisplayDate, formatFullDate } from '@miketromba/issy-core'
2
+ import type { Issue } from '../App'
3
+ import { Badge } from './Badge'
4
4
 
5
5
  interface IssueListProps {
6
- issues: Issue[];
7
- selectedId: string | null;
8
- onSelect: (id: string) => void;
6
+ issues: Issue[]
7
+ selectedId: string | null
8
+ onSelect: (id: string) => void
9
9
  }
10
10
 
11
11
  export function IssueList({ issues, selectedId, onSelect }: IssueListProps) {
12
12
  if (issues.length === 0) {
13
13
  return (
14
- <div className="px-5 py-6 text-text-muted text-sm">
15
- No issues found
16
- </div>
17
- );
14
+ <div className="px-5 py-6 text-text-muted text-sm">No issues found</div>
15
+ )
18
16
  }
19
17
 
20
18
  return (
21
19
  <>
22
20
  {issues.map((issue) => {
23
- const isSelected = issue.id === selectedId;
24
-
21
+ const isSelected = issue.id === selectedId
22
+
25
23
  return (
26
24
  <button
27
25
  key={issue.id}
28
26
  onClick={() => onSelect(issue.id)}
29
27
  className={`block w-full px-5 py-4 border-0 border-b border-border-subtle bg-transparent text-left cursor-pointer transition-colors hover:bg-surface ${
30
- isSelected ? "bg-surface-elevated" : ""
28
+ isSelected ? 'bg-surface-elevated' : ''
31
29
  }`}
32
30
  >
33
31
  <div className="flex items-baseline gap-2 mb-1.5">
34
32
  <span className="text-sm font-medium text-text-primary leading-snug flex-1 min-w-0 line-clamp-2">
35
- {issue.frontmatter.title || "Untitled"}
33
+ {issue.frontmatter.title || 'Untitled'}
36
34
  </span>
37
35
  <span className="font-mono text-xs text-text-muted shrink-0 ml-1">
38
36
  #{issue.id}
39
37
  </span>
40
38
  </div>
41
-
39
+
42
40
  {issue.frontmatter.description && (
43
41
  <div className="text-[13px] text-text-muted mb-2.5 line-clamp-1">
44
42
  {issue.frontmatter.description}
45
43
  </div>
46
44
  )}
47
-
45
+
48
46
  <div className="flex items-center gap-2 flex-wrap">
49
47
  {issue.frontmatter.priority && (
50
48
  <Badge variant="priority" value={issue.frontmatter.priority} />
51
49
  )}
52
-
50
+
53
51
  {issue.frontmatter.status && (
54
52
  <Badge variant="status" value={issue.frontmatter.status} />
55
53
  )}
56
-
54
+
57
55
  {issue.frontmatter.type && (
58
56
  <Badge variant="type" value={issue.frontmatter.type} />
59
57
  )}
60
-
58
+
61
59
  {issue.frontmatter.created && (
62
- <span
60
+ <span
63
61
  className="text-xs text-text-muted"
64
62
  title={formatFullDate(issue.frontmatter.created)}
65
63
  >
@@ -68,8 +66,8 @@ export function IssueList({ issues, selectedId, onSelect }: IssueListProps) {
68
66
  )}
69
67
  </div>
70
68
  </button>
71
- );
69
+ )
72
70
  })}
73
71
  </>
74
- );
72
+ )
75
73
  }
@@ -1,160 +1,156 @@
1
1
  interface QueryHelpModalProps {
2
- isOpen: boolean
3
- onClose: () => void
2
+ isOpen: boolean
3
+ onClose: () => void
4
4
  }
5
5
 
6
6
  export function QueryHelpModal({ isOpen, onClose }: QueryHelpModalProps) {
7
- if (!isOpen) return null
7
+ if (!isOpen) return null
8
8
 
9
- return (
10
- <div
11
- className="fixed inset-0 bg-black/70 flex items-center justify-center z-[1000] p-5"
12
- onClick={onClose}
13
- >
14
- <div
15
- className="bg-surface border border-border rounded-xl max-w-[600px] w-full max-h-[65vh] overflow-y-auto shadow-2xl custom-scrollbar"
16
- onClick={e => e.stopPropagation()}
17
- >
18
- <div className="flex items-center justify-between px-6 py-5 border-b border-border">
19
- <h2 className="text-lg font-semibold text-text-primary">
20
- Query Syntax Help
21
- </h2>
22
- <button
23
- onClick={onClose}
24
- aria-label="Close"
25
- className="w-8 h-8 flex items-center justify-center bg-transparent border-0 rounded-md text-text-muted text-2xl cursor-pointer transition-all hover:bg-surface-elevated hover:text-text-primary"
26
- >
27
- ×
28
- </button>
29
- </div>
9
+ return (
10
+ <div
11
+ className="fixed inset-0 bg-black/70 flex items-center justify-center z-[1000] p-5"
12
+ onClick={onClose}
13
+ >
14
+ <div
15
+ className="bg-surface border border-border rounded-xl max-w-[600px] w-full max-h-[65vh] overflow-y-auto shadow-2xl custom-scrollbar"
16
+ onClick={(e) => e.stopPropagation()}
17
+ >
18
+ <div className="flex items-center justify-between px-6 py-5 border-b border-border">
19
+ <h2 className="text-lg font-semibold text-text-primary">
20
+ Query Syntax Help
21
+ </h2>
22
+ <button
23
+ onClick={onClose}
24
+ aria-label="Close"
25
+ className="w-8 h-8 flex items-center justify-center bg-transparent border-0 rounded-md text-text-muted text-2xl cursor-pointer transition-all hover:bg-surface-elevated hover:text-text-primary"
26
+ >
27
+ ×
28
+ </button>
29
+ </div>
30
30
 
31
- <div className="p-6">
32
- <p className="text-text-secondary mb-6 leading-relaxed">
33
- Use qualifiers to filter issues, or type freely to
34
- search by text.
35
- </p>
31
+ <div className="p-6">
32
+ <p className="text-text-secondary mb-6 leading-relaxed">
33
+ Use qualifiers to filter issues, or type freely to search by text.
34
+ </p>
36
35
 
37
- <section className="mb-6">
38
- <h3 className="text-[13px] font-semibold text-text-muted uppercase tracking-wide mb-3">
39
- Qualifiers
40
- </h3>
41
- <div className="flex flex-col gap-2">
42
- <QualifierRow
43
- qualifier="is:"
44
- description="Filter by status"
45
- values="open, closed"
46
- />
47
- <QualifierRow
48
- qualifier="priority:"
49
- description="Filter by priority"
50
- values="high, medium, low"
51
- />
52
- <QualifierRow
53
- qualifier="type:"
54
- description="Filter by issue type"
55
- values="bug, feature, task, etc."
56
- />
57
- <QualifierRow
58
- qualifier="label:"
59
- description="Filter by label"
60
- values="any label name"
61
- />
62
- <QualifierRow
63
- qualifier="sort:"
64
- description="Sort results"
65
- values="created, priority, title"
66
- />
67
- </div>
68
- </section>
36
+ <section className="mb-6">
37
+ <h3 className="text-[13px] font-semibold text-text-muted uppercase tracking-wide mb-3">
38
+ Qualifiers
39
+ </h3>
40
+ <div className="flex flex-col gap-2">
41
+ <QualifierRow
42
+ qualifier="is:"
43
+ description="Filter by status"
44
+ values="open, closed"
45
+ />
46
+ <QualifierRow
47
+ qualifier="priority:"
48
+ description="Filter by priority"
49
+ values="high, medium, low"
50
+ />
51
+ <QualifierRow
52
+ qualifier="type:"
53
+ description="Filter by issue type"
54
+ values="bug, feature, task, etc."
55
+ />
56
+ <QualifierRow
57
+ qualifier="label:"
58
+ description="Filter by label"
59
+ values="any label name"
60
+ />
61
+ <QualifierRow
62
+ qualifier="sort:"
63
+ description="Sort results"
64
+ values="created, priority, title"
65
+ />
66
+ </div>
67
+ </section>
69
68
 
70
- <section className="mb-6">
71
- <h3 className="text-[13px] font-semibold text-text-muted uppercase tracking-wide mb-3">
72
- Examples
73
- </h3>
74
- <div className="flex flex-col gap-2">
75
- <ExampleRow
76
- code="is:open priority:high"
77
- description="High priority open issues"
78
- />
79
- <ExampleRow
80
- code="type:bug dashboard"
81
- description='Bugs mentioning "dashboard"'
82
- />
83
- <ExampleRow
84
- code="is:open sort:priority"
85
- description="Open issues sorted by priority"
86
- />
87
- <ExampleRow
88
- code="kubernetes cluster"
89
- description='Issues matching "kubernetes cluster"'
90
- />
91
- </div>
92
- </section>
69
+ <section className="mb-6">
70
+ <h3 className="text-[13px] font-semibold text-text-muted uppercase tracking-wide mb-3">
71
+ Examples
72
+ </h3>
73
+ <div className="flex flex-col gap-2">
74
+ <ExampleRow
75
+ code="is:open priority:high"
76
+ description="High priority open issues"
77
+ />
78
+ <ExampleRow
79
+ code="type:bug dashboard"
80
+ description='Bugs mentioning "dashboard"'
81
+ />
82
+ <ExampleRow
83
+ code="is:open sort:priority"
84
+ description="Open issues sorted by priority"
85
+ />
86
+ <ExampleRow
87
+ code="kubernetes cluster"
88
+ description='Issues matching "kubernetes cluster"'
89
+ />
90
+ </div>
91
+ </section>
93
92
 
94
- <section>
95
- <h3 className="text-[13px] font-semibold text-text-muted uppercase tracking-wide mb-3">
96
- Tips
97
- </h3>
98
- <ul className="list-none p-0 m-0 flex flex-col gap-2">
99
- <li className="text-text-secondary text-[13px] pl-4 relative before:content-['•'] before:absolute before:left-0 before:text-text-muted">
100
- Combine multiple qualifiers to narrow results
101
- </li>
102
- <li className="text-text-secondary text-[13px] pl-4 relative before:content-['•'] before:absolute before:left-0 before:text-text-muted">
103
- Free text searches titles, descriptions, and
104
- content
105
- </li>
106
- <li className="text-text-secondary text-[13px] pl-4 relative before:content-[''] before:absolute before:left-0 before:text-text-muted">
107
- Use quotes for multi-word searches:{' '}
108
- <code className="bg-surface-elevated px-1.5 py-0.5 rounded text-xs font-mono">
109
- "api error"
110
- </code>
111
- </li>
112
- <li className="text-text-secondary text-[13px] pl-4 relative before:content-['•'] before:absolute before:left-0 before:text-text-muted">
113
- Qualifiers are case-insensitive
114
- </li>
115
- </ul>
116
- </section>
117
- </div>
118
- </div>
119
- </div>
120
- )
93
+ <section>
94
+ <h3 className="text-[13px] font-semibold text-text-muted uppercase tracking-wide mb-3">
95
+ Tips
96
+ </h3>
97
+ <ul className="list-none p-0 m-0 flex flex-col gap-2">
98
+ <li className="text-text-secondary text-[13px] pl-4 relative before:content-['•'] before:absolute before:left-0 before:text-text-muted">
99
+ Combine multiple qualifiers to narrow results
100
+ </li>
101
+ <li className="text-text-secondary text-[13px] pl-4 relative before:content-['•'] before:absolute before:left-0 before:text-text-muted">
102
+ Free text searches titles, descriptions, and content
103
+ </li>
104
+ <li className="text-text-secondary text-[13px] pl-4 relative before:content-['•'] before:absolute before:left-0 before:text-text-muted">
105
+ Use quotes for multi-word searches:{' '}
106
+ <code className="bg-surface-elevated px-1.5 py-0.5 rounded text-xs font-mono">
107
+ "api error"
108
+ </code>
109
+ </li>
110
+ <li className="text-text-secondary text-[13px] pl-4 relative before:content-['•'] before:absolute before:left-0 before:text-text-muted">
111
+ Qualifiers are case-insensitive
112
+ </li>
113
+ </ul>
114
+ </section>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ )
121
119
  }
122
120
 
123
121
  function QualifierRow({
124
- qualifier,
125
- description,
126
- values
122
+ qualifier,
123
+ description,
124
+ values,
127
125
  }: {
128
- qualifier: string
129
- description: string
130
- values: string
126
+ qualifier: string
127
+ description: string
128
+ values: string
131
129
  }) {
132
- return (
133
- <div className="flex gap-3 px-3 py-2.5 bg-surface-elevated rounded-md">
134
- <code className="font-mono text-[13px] text-accent shrink-0 min-w-[80px]">
135
- {qualifier}
136
- </code>
137
- <span className="text-text-secondary text-[13px] flex flex-col gap-0.5">
138
- {description}
139
- <span className="text-text-muted text-xs">{values}</span>
140
- </span>
141
- </div>
142
- )
130
+ return (
131
+ <div className="flex gap-3 px-3 py-2.5 bg-surface-elevated rounded-md">
132
+ <code className="font-mono text-[13px] text-accent shrink-0 min-w-[80px]">
133
+ {qualifier}
134
+ </code>
135
+ <span className="text-text-secondary text-[13px] flex flex-col gap-0.5">
136
+ {description}
137
+ <span className="text-text-muted text-xs">{values}</span>
138
+ </span>
139
+ </div>
140
+ )
143
141
  }
144
142
 
145
143
  function ExampleRow({
146
- code,
147
- description
144
+ code,
145
+ description,
148
146
  }: {
149
- code: string
150
- description: string
147
+ code: string
148
+ description: string
151
149
  }) {
152
- return (
153
- <div className="flex flex-col gap-1 px-3 py-2.5 bg-surface-elevated rounded-md">
154
- <code className="font-mono text-[13px] text-text-primary">
155
- {code}
156
- </code>
157
- <span className="text-text-muted text-xs">{description}</span>
158
- </div>
159
- )
150
+ return (
151
+ <div className="flex flex-col gap-1 px-3 py-2.5 bg-surface-elevated rounded-md">
152
+ <code className="font-mono text-[13px] text-text-primary">{code}</code>
153
+ <span className="text-text-muted text-xs">{description}</span>
154
+ </div>
155
+ )
160
156
  }
package/src/frontend.tsx CHANGED
@@ -7,7 +7,9 @@ import { App } from './App'
7
7
  import './index.css'
8
8
 
9
9
  function start() {
10
- const root = createRoot(document.getElementById('root')!)
10
+ const container = document.getElementById('root')
11
+ if (!container) throw new Error('Root element not found')
12
+ const root = createRoot(container)
11
13
  root.render(<App />)
12
14
  }
13
15
 
package/src/index.css CHANGED
@@ -49,15 +49,15 @@
49
49
 
50
50
  /* Code block styling - remove borders, unify background, add dark scrollbar */
51
51
  .prose pre {
52
- border: none !important;
53
- box-shadow: none !important;
54
- background-color: var(--color-surface-elevated) !important;
52
+ border: none;
53
+ box-shadow: none;
54
+ background-color: var(--color-surface-elevated);
55
55
  }
56
56
 
57
57
  .prose pre code.hljs {
58
- background: transparent !important;
59
- padding: 0 !important;
60
- font-size: 13px !important;
58
+ background: transparent;
59
+ padding: 0;
60
+ font-size: 13px;
61
61
  }
62
62
 
63
63
  .prose pre::-webkit-scrollbar {