hale-commenting-system 3.6.0 → 3.7.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.
package/README.md CHANGED
@@ -2,42 +2,29 @@
2
2
 
3
3
  A commenting system for PatternFly React applications that allows designers and developers to add comments directly on design pages, sync with GitHub Issues, and link Jira tickets.
4
4
 
5
- ## 🚨 **CRITICAL: Read Before Uninstalling**
5
+ ![Default Widget](./screenshots/defaultWidget.png)
6
6
 
7
- **This package modifies your project files during setup.**
7
+ ## 🚨 Important: Proper Uninstall Required
8
8
 
9
- **BEFORE uninstalling, you MUST run:**
9
+ **This package modifies your project files during setup.** Before uninstalling, you MUST run:
10
10
  ```bash
11
11
  npx hale-commenting-system remove
12
12
  npm uninstall hale-commenting-system
13
13
  ```
14
14
 
15
- **Failure to run `remove` first will break your app!** See the [Uninstalling](#uninstalling) section below.
16
-
17
- ## Features
18
-
19
- - **React Component-Based Commenting** - Detects and attaches comments to React components, not just DOM elements
20
- - Automatically identifies React component names, types, and props
21
- - Shows component tree path (e.g., "App > Dashboard > Button")
22
- - Displays component metadata including props in the comment panel
23
- - **Hover Preview** - See what will be selected before clicking with a visual preview
24
- - Dashed blue border highlights the element you're about to select
25
- - Component name label appears above the element
26
- - Works on all elements including buttons, links, and interactive components
27
- - **Smart Pin Positioning** - Pins anchor precisely to elements using CSS selectors
28
- - Pins appear at the top-left corner of selected components
29
- - Automatically follows elements on scroll and resize
30
- - Falls back to stored coordinates if element is deleted
31
- - **Component Highlighting** - Selected components are highlighted with a blue border (similar to Chrome DevTools)
32
- - **Show Pins Toggle** - View comment pins even when commenting is disabled
33
- - **Resizable Widget** - Adjust the commenting panel size to fit your workflow
34
- - **Missing Element Detection** - Pins fade and show [deleted] when target component is removed
35
- - **Thread Discussions** - Organize comments into threads with replies
36
- - **GitHub Integration** - Sync comments with GitHub Issues automatically
37
- - **Jira Integration** - Link Jira tickets to specific pages or sections
38
- - **PatternFly Design** - Built with PatternFly React components
39
- - **Responsive** - Works on desktop and mobile devices
40
- - **Easy Integration** - Automated setup script for seamless installation
15
+ Failure to run `remove` first will break your app. See [Uninstalling](#uninstalling) for details.
16
+
17
+ ## Key Features
18
+
19
+ - **React Component Detection** - Automatically identifies React components with names, types, props, and component tree paths
20
+ - **Smart Pin Positioning** - Pins anchor to elements using CSS selectors and follow them on scroll/resize
21
+ - **Hover Preview** - Visual preview with dashed border before creating a comment
22
+ - **Component Highlighting** - Chrome DevTools-style blue border on selected components
23
+ - **Resizable Widget** - Adjustable panel size (300-800px width)
24
+ - **Thread Discussions** - Organized comment threads with replies
25
+ - **GitHub Integration** - Automatic sync with GitHub Issues including component metadata
26
+ - **Jira Integration** - Link tickets to specific pages or sections
27
+ - **Missing Element Detection** - Pins fade and show [deleted] when target is removed
41
28
 
42
29
  ## Prerequisites
43
30
 
@@ -48,353 +35,171 @@ This package was developed for **PatternFly React Seed** projects, but can be us
48
35
 
49
36
  The automated integration script (`npx hale-commenting-system init`) works best with PatternFly React Seed or projects with a similar structure.
50
37
 
51
- ## Installation and Setup
52
-
53
- > **⚠️ Note:** This package modifies your project files during setup. See the [Uninstalling](#uninstalling) section for proper removal instructions.
38
+ ## Quick Start
54
39
 
55
- 1. **Install the package:**
56
- ```bash
57
- npm install hale-commenting-system
58
- ```
40
+ ```bash
41
+ # Install
42
+ npm install hale-commenting-system
59
43
 
60
- 2. **Run the integration script:**
61
- ```bash
62
- npx hale-commenting-system init
63
- ```
44
+ # Run setup
45
+ npx hale-commenting-system init
64
46
 
65
- 3. **Follow the interactive setup:**
66
- - The script will guide you through project setup
67
- - Optionally configure GitHub OAuth integration
68
- - Optionally configure Jira integration
69
- - Configuration files (`.env` and `.env.server`) will be created automatically
47
+ # Start dev server
48
+ npm run start:dev
49
+ ```
70
50
 
71
- 4. **Start your dev server:**
72
- ```bash
73
- npm run start:dev
74
- ```
51
+ The interactive setup will guide you through configuring GitHub OAuth and Jira integration (both optional).
75
52
 
76
53
  ## Uninstalling
77
54
 
78
- **⚠️ IMPORTANT: Two-Step Process Required**
79
-
80
- You **MUST** run the removal script before uninstalling to avoid breaking your app:
81
-
82
- ### Step 1: Remove Integration
55
+ **Always remove integration before uninstalling:**
83
56
 
84
57
  ```bash
58
+ # Step 1: Remove integration
85
59
  npx hale-commenting-system remove
86
- ```
87
-
88
- This removes all integration code from your project files.
89
-
90
- ### Step 2: Uninstall Package
91
60
 
92
- ```bash
61
+ # Step 2: Uninstall package
93
62
  npm uninstall hale-commenting-system
94
- ```
95
-
96
- ### Step 3: Restart Dev Server
97
63
 
98
- ```bash
64
+ # Step 3: Restart
99
65
  npm run start:dev
100
66
  ```
101
67
 
102
- ### What If I Forgot and Already Uninstalled?
103
-
104
- If you uninstalled without running `remove` first and your app is now broken:
105
-
106
- 1. **Reinstall the package:**
107
- ```bash
108
- npm install hale-commenting-system
109
- ```
68
+ ### Already Uninstalled by Mistake?
110
69
 
111
- 2. **Run the removal script:**
112
- ```bash
113
- npx hale-commenting-system remove
114
- ```
70
+ If your app is broken after uninstalling without running `remove`:
115
71
 
116
- 3. **Uninstall again:**
117
- ```bash
118
- npm uninstall hale-commenting-system
119
- ```
120
-
121
- 4. **Restart your dev server:**
122
- ```bash
123
- npm run start:dev
124
- ```
125
-
126
- ### What the Removal Script Does
72
+ ```bash
73
+ npm install hale-commenting-system
74
+ npx hale-commenting-system remove
75
+ npm uninstall hale-commenting-system
76
+ ```
127
77
 
128
- The removal script will automatically:
129
- - Remove imports from `src/app/index.tsx`
130
- - Remove imports from `src/app/AppLayout/AppLayout.tsx`
131
- - Notify you about webpack middleware (may require manual removal)
132
- - Keep `.env` and `.env.server` files (you can delete manually if needed)
78
+ <details>
79
+ <summary>What the removal script does</summary>
133
80
 
134
- ### Manual Uninstall (If Needed)
81
+ - Removes imports from `src/app/index.tsx`
82
+ - Removes imports from `src/app/AppLayout/AppLayout.tsx`
83
+ - Notifies about webpack middleware (may require manual removal)
84
+ - Keeps `.env` and `.env.server` files
85
+ </details>
135
86
 
136
- If you need to manually remove the integration:
87
+ ## Usage
137
88
 
138
- 1. **In `src/app/index.tsx`**, remove:
139
- ```typescript
140
- import { CommentProvider, GitHubAuthProvider } from "hale-commenting-system";
141
- ```
142
- And remove the `<GitHubAuthProvider>` and `<CommentProvider>` wrappers.
89
+ ### Creating Comments
143
90
 
144
- 2. **In `src/app/AppLayout/AppLayout.tsx`**, remove:
145
- ```typescript
146
- import { CommentPanel, CommentOverlay } from "hale-commenting-system";
147
- ```
148
- And remove the `<CommentPanel>` and `<CommentOverlay />` components.
91
+ 1. **Hover** over any component to see a preview with a dashed blue border and component label
92
+ 2. **Click** to attach a comment pin - the system detects React components automatically
93
+ 3. **View** component details in the panel including name, type, tree path, and props
149
94
 
150
- 3. **In `webpack.dev.js`**, remove the middleware configuration added for GitHub OAuth and Jira API proxying.
95
+ ![Component Details](./screenshots/DesignDetails1.png)
151
96
 
152
- ## Usage
97
+ ### Managing Comments
153
98
 
154
- After running the integration script, the commenting system will be available in your PatternFly React Seed application.
155
-
156
- ### Adding Comments
157
-
158
- 1. **Hover to preview** - Move your mouse over any component to see a preview of what will be selected
159
- - A dashed blue border highlights the element
160
- - A label shows the component or element name
161
- - This helps you target the exact component you want to comment on
162
-
163
- 2. **Click on any component** to attach a comment pin
164
- - The system detects React components automatically
165
- - Falls back to DOM element detection for non-React elements
166
- - Pins anchor to specific elements using CSS selectors
167
- - Pins appear at the top-left corner of the selected component
168
-
169
- 3. **View component information** in the comment panel
170
- - **React Components:** Shows component name, type (function/class/memo), component tree path, and props
171
- - **DOM Elements:** Shows element description (e.g., "button.pf-c-button")
172
- - Components marked as [deleted] if the element is removed from the page
173
- - Expandable props section shows all component props in formatted JSON
174
-
175
- 4. **Pin behavior:**
176
- - **Element exists:** Pin positions at top-left corner and follows it on scroll/resize
177
- - **Element deleted:** Pin fades to 40% opacity, uses fallback position, and shows [deleted] label
178
- - **Selected pin:** Highlighted with blue border around the target component
179
-
180
- 5. **Control visibility:**
181
- - **Enable Comments:** Toggle to enable/disable creating new comments
182
- - **Show pins:** Toggle to show/hide pins even when commenting is disabled
183
- - Use both toggles to view existing comments without creating new ones
184
-
185
- 6. **Resize the widget:**
186
- - Drag the resize handle in the bottom-right corner of the commenting panel
187
- - Adjust width (300-800px) and height (200px to viewport height)
188
- - Size is maintained during your session
189
-
190
- 7. **Navigate and manage comments:**
191
- - **View all comments** in the "Comments" menu item in the sidebar
192
- - **Reply to comments** to create discussion threads
193
- - **Select pins** by clicking on them to view/edit comments
194
- - **Close/reopen threads** just like GitHub Issues
195
- - **Remove pins** to delete comment threads
99
+ - **Toggle visibility** - Enable/disable comments or use "Show pins" to view without creating new ones
100
+ - **Resize widget** - Drag the resize handle (300-800px width, 200px to viewport height)
101
+ - **Pin behavior** - Pins follow elements on scroll/resize; fade to 40% opacity if element is deleted
102
+ - **Thread discussions** - Reply to comments, close/reopen threads, view all in sidebar
103
+ - **Remove pins** - Delete comment threads as needed
196
104
 
197
105
  ### How It Works
198
106
 
199
- The commenting system uses a **hybrid approach** combining React component detection and CSS selectors:
107
+ The system uses a **hybrid approach**:
200
108
 
201
- #### React Component Detection (Primary)
109
+ **React Component Detection** (Primary)
110
+ - Detects components using React fiber nodes
111
+ - Extracts name, type, props, and component tree path
112
+ - Works with all React component types including HOCs
202
113
 
203
- 1. **Component identification:**
204
- - Detects React components using React fiber nodes
205
- - Extracts component name, type (function/class/memo/forwardRef), and props
206
- - Builds component tree path showing hierarchy (e.g., "App > Dashboard > Button")
207
- - Works with all React component types including HOCs and wrapped components
114
+ **CSS Selector Fallback**
115
+ - Uses `data-testid`, `id`, or tag + class + aria attributes
116
+ - Stores coordinates as fallback if element is deleted
208
117
 
209
- 2. **Component metadata:**
210
- - Component name (e.g., "Button", "Card", "CustomComponent")
211
- - Component type (function, class, memo, forwardRef, etc.)
212
- - Component props (sanitized for display, functions shown as [Function])
213
- - Component tree path showing parent hierarchy
118
+ ### Integrations
214
119
 
215
- #### CSS Selector Fallback
120
+ **GitHub** (Optional)
121
+ - Comments sync as GitHub Issues with component metadata
122
+ - Replies sync as issue comments
123
+ - Status changes (open/closed) sync bidirectionally
216
124
 
217
- For non-React elements or when component detection isn't available:
218
-
219
- 1. **Priority selection strategy:**
220
- - `data-testid` or `data-id` attributes (most stable)
221
- - `id` attribute
222
- - Combination of tag + class + aria attributes
223
- - Fallback to nth-child path
125
+ **Jira** (Optional)
126
+ - Link Jira tickets to specific pages or sections
127
+ - View ticket details in the commenting panel
128
+ - Track design work alongside development
224
129
 
225
- 2. **Stored metadata for each pin:**
226
- - CSS selector for the target element
227
- - React component metadata (if available)
228
- - Simplified element/component name for display
229
- - Fallback coordinates (if element is deleted)
130
+ ![Jira Integration](./screenshots/jira.png)
230
131
 
231
- 3. **Example:** Clicking a React Button component might show:
232
- - **Component Name:** `Button`
233
- - **Component Type:** `function`
234
- - **Component Path:** `App > Dashboard > Button`
235
- - **Props:** `{ variant: "primary", children: "Submit" }`
236
- - **CSS Selector:** `button.pf-c-button[aria-label="Submit"]` (fallback)
132
+ ## Configuration
237
133
 
238
- ### GitHub Integration (Optional)
134
+ **`.env`** (client-side, safe to commit)
135
+ - GitHub OAuth client ID, Jira base URL
239
136
 
240
- When configured, comments automatically sync with GitHub Issues:
241
- - Each comment thread becomes a GitHub Issue with component metadata
242
- - Issue body includes: React component name (if available), CSS selector, and fallback position
243
- - Replies sync as Issue comments with proper threading
244
- - Status changes (open/closed) sync between the app and GitHub
245
- - Component metadata is preserved in issue descriptions
137
+ **`.env.server`** (server-side secrets, auto-added to `.gitignore`)
138
+ - GitHub OAuth client secret, Jira API tokens
246
139
 
247
- ### Jira Integration (Optional)
140
+ ## Requirements
248
141
 
249
- When configured, you can:
250
- - Link Jira tickets to specific pages or sections
251
- - View ticket details in the commenting panel
252
- - Track design work alongside development tickets
142
+ - PatternFly React Seed project (or compatible PatternFly React app)
143
+ - Node.js 18+ (for webpack middleware with native `fetch()`)
144
+ - React 18+
253
145
 
254
- ## Configuration
146
+ ## What Gets Modified
255
147
 
256
- The integration script creates two configuration files:
148
+ The `init` script automatically updates:
149
+ - `src/app/index.tsx` - Adds providers
150
+ - `src/app/routes.tsx` - Adds Comments route
151
+ - `src/app/AppLayout/AppLayout.tsx` - Adds panel and overlay components
152
+ - `webpack.dev.js` - Adds OAuth/Jira proxy middleware
153
+ - Creates `src/app/Comments/Comments.tsx` and config files
257
154
 
258
- ### `.env`
259
- Contains client-side configuration (safe to commit):
260
- - GitHub OAuth client ID
261
- - Jira base URL
262
- - Other public configuration
155
+ ## Local Testing
263
156
 
264
- ### `.env.server`
265
- Contains server-side secrets (should NOT be committed):
266
- - GitHub OAuth client secret
267
- - Jira API tokens
268
- - Other sensitive credentials
157
+ <details>
158
+ <summary>Click to expand testing instructions</summary>
269
159
 
270
- **Important:** The `.env.server` file is automatically added to `.gitignore` to prevent committing secrets.
160
+ ### Using npm link (Recommended)
271
161
 
272
- See the generated files for detailed setup instructions.
273
-
274
- ## Requirements
162
+ ```bash
163
+ # In package directory
164
+ npm run build && npm link
275
165
 
276
- - **PatternFly React Seed** project (or compatible PatternFly React application)
277
- - **Node.js 18+** (required for webpack middleware with native `fetch()` support)
278
- - **React 18+**
279
-
280
- ## What Gets Integrated
281
-
282
- The integration script automatically modifies your project:
283
-
284
- 1. **`src/app/index.tsx`** - Adds `CommentProvider` and `GitHubAuthProvider`
285
- 2. **`src/app/routes.tsx`** - Adds "Comments" route group with "View all" route
286
- 3. **`src/app/AppLayout/AppLayout.tsx`** - Adds `CommentPanel` and `CommentOverlay` components
287
- 4. **`webpack.dev.js`** - Adds middleware for GitHub OAuth and Jira API proxying
288
- 5. **`src/app/Comments/Comments.tsx`** - Creates the Comments view component
289
- 6. **`.env` and `.env.server`** - Creates configuration files
290
-
291
- ## Testing Before Publishing
292
-
293
- To test this package locally before publishing to npm:
294
-
295
- ### Method 1: Using npm link (Recommended)
296
-
297
- 1. **In the package directory** (hale-npm-package):
298
- ```bash
299
- npm run build
300
- npm link
301
- ```
302
-
303
- 2. **In your test application** (e.g., PatternFly React Seed):
304
- ```bash
305
- npm link hale-commenting-system
306
- npx hale-commenting-system init
307
- npm run start:dev
308
- ```
309
-
310
- 3. **Test the features:**
311
- - Hover over components to see preview highlighting
312
- - Click on various components (buttons, cards, inputs)
313
- - Verify pins attach to components and appear at top-left corner
314
- - Check that React component metadata is displayed correctly
315
- - Scroll/resize and verify pins follow elements
316
- - Add comments and replies
317
- - Test "Show pins" toggle to view pins without enabling comments
318
- - Resize the commenting widget
319
- - Remove a component from the DOM and verify pin fades and shows [deleted]
320
- - Reload page and verify pins reattach correctly
321
-
322
- 4. **When done testing:**
323
- ```bash
324
- # In your test app
325
- npx hale-commenting-system remove
326
- npm unlink hale-commenting-system
327
- npm install
328
-
329
- # In the package directory
330
- npm unlink
331
- ```
332
-
333
- ### Method 2: Using npm pack
334
-
335
- 1. **In the package directory:**
336
- ```bash
337
- npm run build
338
- npm pack
339
- ```
340
- This creates a `.tgz` file (e.g., `hale-commenting-system-3.6.0.tgz`)
341
-
342
- 2. **In your test application:**
343
- ```bash
344
- npm install /path/to/hale-commenting-system-3.5.2.tgz
345
- npx hale-commenting-system init
346
- npm run start:dev
347
- ```
348
-
349
- 3. **Test and clean up as above**
350
-
351
- ### Method 3: Using Local Path
352
-
353
- 1. **In your test application:**
354
- ```bash
355
- npm install /path/to/hale-npm-package
356
- npx hale-commenting-system init
357
- npm run start:dev
358
- ```
359
-
360
- ### What to Test
361
-
362
- - ✅ **Hover preview** - Verify preview shows correct element and component name
363
- - ✅ **Pin creation** - Click on different component types (React and DOM elements)
364
- - ✅ **Pin positioning** - Verify pins appear at top-left corner of elements
365
- - ✅ **Component detection** - Verify React components show name, type, path, and props
366
- - ✅ **Component highlighting** - Verify selected components are highlighted with blue border
367
- - ✅ **Show pins toggle** - Verify pins are visible when toggle is on, even with comments disabled
368
- - ✅ **Widget resizing** - Verify widget can be resized and maintains size
369
- - ✅ **Dynamic tracking** - Scroll, resize, and verify pins follow elements
370
- - ✅ **Element deletion** - Remove components and verify fade + [deleted] label
371
- - ✅ **Page reload** - Verify pins reattach to correct elements
372
- - ✅ **Component display** - Check panel shows React component info or DOM element names
373
- - ✅ **GitHub sync** (if configured) - Verify issues include component metadata
374
- - ✅ **Comments & replies** - Test threaded discussions
375
- - ✅ **Close/reopen** - Test thread status changes
376
- - ✅ **Remove pin** - Verify removing a pin doesn't create a new one
166
+ # In test app
167
+ npm link hale-commenting-system
168
+ npx hale-commenting-system init
169
+ npm run start:dev
377
170
 
378
- ## Development
171
+ # Clean up when done
172
+ npx hale-commenting-system remove
173
+ npm unlink hale-commenting-system && npm install
174
+ ```
379
175
 
380
- ### Running Locally
176
+ ### Using npm pack
381
177
 
382
178
  ```bash
383
- # Install dependencies
384
- npm install
179
+ # In package directory
180
+ npm run build && npm pack
385
181
 
386
- # Start development server
182
+ # In test app
183
+ npm install /path/to/hale-commenting-system-*.tgz
184
+ npx hale-commenting-system init
387
185
  npm run start:dev
388
186
  ```
389
187
 
390
- ### Building for Production
188
+ ### Test Checklist
189
+ - Hover preview and pin creation
190
+ - Component detection (name, type, path, props)
191
+ - Pin positioning and dynamic tracking
192
+ - Widget resizing and toggles
193
+ - Element deletion handling
194
+ - Comments, replies, and GitHub/Jira sync
195
+ </details>
391
196
 
392
- ```bash
393
- # Run production build
394
- npm run build
197
+ ## Development
395
198
 
396
- # Start production server
397
- npm run start
199
+ ```bash
200
+ npm install # Install dependencies
201
+ npm run start:dev # Start dev server
202
+ npm run build # Production build
398
203
  ```
399
204
 
400
205
  ## License
@@ -403,4 +208,4 @@ MIT
403
208
 
404
209
  ## Support
405
210
 
406
- For issues or questions, please open an issue on npm or contact the package maintainer.
211
+ For issues or questions, please open an issue on GitHub or npm.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hale-commenting-system",
3
- "version": "3.6.0",
3
+ "version": "3.7.1",
4
4
  "description": "A commenting system for PatternFly React applications that allows designers and developers to add comments directly on design pages, sync with GitHub Issues, and link Jira tickets.",
5
5
  "homepage": "https://www.npmjs.com/package/hale-commenting-system",
6
6
  "license": "MIT",
@@ -47,8 +47,17 @@ interface LegacyJiraRecord {
47
47
 
48
48
  type JiraStore = Record<string, JiraRecord>;
49
49
 
50
+ // Jira Issue Cache
51
+ type CachedJiraIssue = {
52
+ ticket: JiraTicket;
53
+ fetchedAt: number; // timestamp
54
+ };
55
+ type JiraIssueCache = Record<string, CachedJiraIssue>;
56
+
50
57
  const STORAGE_KEY = 'hale_commenting_jira_v1';
58
+ const CACHE_STORAGE_KEY = 'hale_commenting_jira_cache_v1';
51
59
  const GH_JIRA_PATH = '.hale/jira.json';
60
+ const CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
52
61
 
53
62
  function migrateRecord(record: LegacyJiraRecord): JiraRecord {
54
63
  // If it's already in the new format, return as-is
@@ -100,6 +109,50 @@ function setStore(next: JiraStore) {
100
109
  window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
101
110
  }
102
111
 
112
+ function getIssueCache(): JiraIssueCache {
113
+ if (typeof window === 'undefined') return {};
114
+ try {
115
+ const raw = window.localStorage.getItem(CACHE_STORAGE_KEY);
116
+ if (!raw) return {};
117
+ return JSON.parse(raw) as JiraIssueCache;
118
+ } catch {
119
+ return {};
120
+ }
121
+ }
122
+
123
+ function setIssueCache(cache: JiraIssueCache) {
124
+ if (typeof window === 'undefined') return;
125
+ window.localStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(cache));
126
+ }
127
+
128
+ function getCachedIssue(key: string): JiraTicket | null {
129
+ const cache = getIssueCache();
130
+ const cached = cache[key];
131
+ if (!cached) return null;
132
+
133
+ const now = Date.now();
134
+ const age = now - cached.fetchedAt;
135
+
136
+ // Return cached data if it's still fresh
137
+ if (age < CACHE_TTL_MS) {
138
+ return cached.ticket;
139
+ }
140
+
141
+ // Expired - remove it
142
+ delete cache[key];
143
+ setIssueCache(cache);
144
+ return null;
145
+ }
146
+
147
+ function setCachedIssue(key: string, ticket: JiraTicket) {
148
+ const cache = getIssueCache();
149
+ cache[key] = {
150
+ ticket,
151
+ fetchedAt: Date.now(),
152
+ };
153
+ setIssueCache(cache);
154
+ }
155
+
103
156
  function normalizePathname(pathname: string): string {
104
157
  if (!pathname) return '/';
105
158
  const cleaned = pathname.split('?')[0].split('#')[0];
@@ -339,7 +392,7 @@ export const JiraTab: React.FunctionComponent = () => {
339
392
  // eslint-disable-next-line react-hooks/exhaustive-deps
340
393
  }, []);
341
394
 
342
- // Fetch Jira issue details for all keys.
395
+ // Fetch Jira issue details for all keys (with caching).
343
396
  React.useEffect(() => {
344
397
  const keys = record?.jiraKeys || [];
345
398
  if (keys.length === 0) {
@@ -354,24 +407,50 @@ export const JiraTab: React.FunctionComponent = () => {
354
407
  const results: Record<string, JiraTicket> = {};
355
408
  const errors: Record<string, string> = {};
356
409
 
357
- await Promise.all(
358
- keys.map(async (key) => {
359
- try {
360
- const resp = await fetch(`/api/jira-issue?key=${encodeURIComponent(key)}`);
361
- const payload = await resp.json().catch(() => ({}));
362
- if (!resp.ok) {
363
- const raw = String(payload?.message || `Failed to fetch Jira issue (${resp.status})`);
364
- const sanitized = raw.trim().startsWith('<') ? 'Unauthorized or non-JSON response from Jira.' : raw;
365
- const hint = payload?.hint ? ` ${String(payload.hint)}` : '';
366
- errors[key] = `${sanitized}${hint}`;
367
- return;
410
+ // Check cache first
411
+ const keysToFetch: string[] = [];
412
+ for (const key of keys) {
413
+ const cached = getCachedIssue(key);
414
+ if (cached) {
415
+ results[key] = cached;
416
+ } else {
417
+ keysToFetch.push(key);
418
+ }
419
+ }
420
+
421
+ // Fetch uncached issues
422
+ if (keysToFetch.length > 0) {
423
+ await Promise.all(
424
+ keysToFetch.map(async (key) => {
425
+ try {
426
+ const resp = await fetch(`/api/jira-issue?key=${encodeURIComponent(key)}`);
427
+ const payload = await resp.json().catch(() => ({}));
428
+
429
+ if (!resp.ok) {
430
+ // Handle rate limiting specially
431
+ if (resp.status === 429) {
432
+ errors[key] = 'Rate limit exceeded. Please wait a few minutes and refresh the page.';
433
+ return;
434
+ }
435
+
436
+ const raw = String(payload?.message || `Failed to fetch Jira issue (${resp.status})`);
437
+ const sanitized = raw.trim().startsWith('<') ? 'Unauthorized or non-JSON response from Jira.' : raw;
438
+ const hint = payload?.hint ? ` ${String(payload.hint)}` : '';
439
+ errors[key] = `${sanitized}${hint}`;
440
+ return;
441
+ }
442
+
443
+ const ticket = payload as JiraTicket;
444
+ results[key] = ticket;
445
+
446
+ // Cache the successful result
447
+ setCachedIssue(key, ticket);
448
+ } catch (e: any) {
449
+ errors[key] = e?.message || 'Failed to fetch Jira issue';
368
450
  }
369
- results[key] = payload as JiraTicket;
370
- } catch (e: any) {
371
- errors[key] = e?.message || 'Failed to fetch Jira issue';
372
- }
373
- })
374
- );
451
+ })
452
+ );
453
+ }
375
454
 
376
455
  setIssues(results);
377
456
  setIssueErrors(errors);
@@ -735,7 +814,14 @@ export const JiraTab: React.FunctionComponent = () => {
735
814
 
736
815
  {/* Error state */}
737
816
  {error && !isFetchingIssues && (
738
- <div style={{ fontSize: '0.875rem', color: 'var(--pf-t--global--danger--color--100)' }}>{error}</div>
817
+ <div style={{ display: 'grid', gap: '0.5rem' }}>
818
+ <div style={{ fontSize: '0.875rem', color: 'var(--pf-t--global--danger--color--100)' }}>{error}</div>
819
+ {error.includes('Rate limit') && (
820
+ <div style={{ fontSize: '0.75rem', color: 'var(--pf-t--global--text--color--subtle)' }}>
821
+ Tip: Jira data is cached for 15 minutes to reduce API calls. Refreshing the page will retry.
822
+ </div>
823
+ )}
824
+ </div>
739
825
  )}
740
826
 
741
827
  {/* Issue details */}