@feedlog-ai/webcomponents 0.0.10 → 0.0.12

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 (48) hide show
  1. package/dist/cjs/feedlog-badge.cjs.entry.js +21 -0
  2. package/dist/cjs/{feedlog-badge_3.cjs.entry.js → feedlog-button_2.cjs.entry.js} +2 -19
  3. package/dist/cjs/feedlog-card.cjs.entry.js +1 -1
  4. package/dist/cjs/feedlog-github-issues-client.cjs.entry.js +56 -6
  5. package/dist/cjs/feedlog-github-issues.cjs.entry.js +1 -1
  6. package/dist/cjs/feedlog-issues-list.cjs.entry.js +66 -0
  7. package/dist/cjs/feedlog-toolkit.cjs.js +2 -2
  8. package/dist/cjs/{index-5lluu_3h.js → index-DnsqTPII.js} +0 -37
  9. package/dist/cjs/loader.cjs.js +2 -2
  10. package/dist/collection/collection-manifest.json +2 -1
  11. package/dist/collection/components/feedlog-github-issues-client/feedlog-github-issues-client.js +55 -5
  12. package/dist/collection/components/feedlog-issue/feedlog-issue.js +1 -1
  13. package/dist/collection/components/feedlog-issues-list/feedlog-issues-list.css +227 -0
  14. package/dist/collection/components/feedlog-issues-list/feedlog-issues-list.js +139 -0
  15. package/dist/collection/components/index.js +1 -0
  16. package/dist/components/feedlog-github-issues-client.js +1 -1
  17. package/dist/components/feedlog-github-issues.js +1 -1
  18. package/dist/components/feedlog-issue.js +1 -1
  19. package/dist/components/feedlog-issues-list.d.ts +11 -0
  20. package/dist/components/feedlog-issues-list.js +1 -0
  21. package/dist/components/index.js +1 -1
  22. package/dist/components/p-BLmN8b4F.js +1 -0
  23. package/dist/components/{p-5qPAHrMz.js → p-BxyIMDYk.js} +1 -1
  24. package/dist/esm/feedlog-badge.entry.js +19 -0
  25. package/dist/esm/{feedlog-badge_3.entry.js → feedlog-button_2.entry.js} +3 -19
  26. package/dist/esm/feedlog-card.entry.js +1 -1
  27. package/dist/esm/feedlog-github-issues-client.entry.js +56 -6
  28. package/dist/esm/feedlog-github-issues.entry.js +1 -1
  29. package/dist/esm/feedlog-issues-list.entry.js +64 -0
  30. package/dist/esm/feedlog-toolkit.js +3 -3
  31. package/dist/esm/{index-CkB6Yzeb.js → index-rs_66Oq4.js} +0 -37
  32. package/dist/esm/loader.js +3 -3
  33. package/dist/feedlog-toolkit/feedlog-toolkit.esm.js +1 -1
  34. package/dist/feedlog-toolkit/p-1ff506c3.entry.js +1 -0
  35. package/dist/feedlog-toolkit/p-40ff5447.entry.js +1 -0
  36. package/dist/feedlog-toolkit/{p-cdb2b098.entry.js → p-4e90285c.entry.js} +1 -1
  37. package/dist/feedlog-toolkit/p-50a467a3.entry.js +1 -0
  38. package/dist/feedlog-toolkit/{p-95fea2f4.entry.js → p-774e08fd.entry.js} +1 -1
  39. package/dist/feedlog-toolkit/{p-8c5e3a0b.entry.js → p-f5626239.entry.js} +1 -1
  40. package/dist/feedlog-toolkit/p-rs_66Oq4.js +2 -0
  41. package/dist/types/components/feedlog-github-issues-client/feedlog-github-issues-client.d.ts +7 -0
  42. package/dist/types/components/feedlog-issues-list/feedlog-issues-list.d.ts +31 -0
  43. package/dist/types/components/index.d.ts +1 -0
  44. package/dist/types/components.d.ts +76 -0
  45. package/package.json +2 -2
  46. package/dist/components/p-DaNa3wCt.js +0 -1
  47. package/dist/feedlog-toolkit/p-CkB6Yzeb.js +0 -2
  48. package/dist/feedlog-toolkit/p-bbb8c3b6.entry.js +0 -1
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ var index = require('./index-DnsqTPII.js');
4
+
5
+ const feedlogBadgeCss = () => `:host{display:inline-block;--feedlog-badge-font-size:0.75rem;--feedlog-badge-font-weight:500;--feedlog-badge-padding-x:0.5rem;--feedlog-badge-padding-y:0.125rem;--feedlog-badge-border-radius:calc(0.625rem - 2px);--feedlog-blue-500:oklch(0.623 0.214 259.815);--feedlog-blue-600:oklch(0.546 0.245 262.881);--feedlog-blue-700:oklch(0.488 0.243 264.376);--feedlog-destructive:#d4183d;--feedlog-destructive-hover:#b91c1c}:host(.dark){--feedlog-blue-500:oklch(0.623 0.214 259.815);--feedlog-blue-600:oklch(0.546 0.245 262.881);--feedlog-destructive:oklch(0.396 0.141 25.723);--feedlog-destructive-hover:oklch(0.45 0.16 25.723)}.badge{display:inline-flex;align-items:center;justify-content:center;font-size:var(--feedlog-badge-font-size);font-weight:var(--feedlog-badge-font-weight);padding:var(--feedlog-badge-padding-y) var(--feedlog-badge-padding-x);border-radius:var(--feedlog-badge-border-radius);white-space:nowrap;transition:background-color 0.15s ease;font-family:inherit}.badge-default{background-color:var(--feedlog-blue-600);color:#ffffff}.badge-default:hover{background-color:var(--feedlog-blue-700)}:host(.dark) .badge-default{background-color:var(--feedlog-blue-500)}:host(.dark) .badge-default:hover{background-color:var(--feedlog-blue-600)}.badge-enhancement{background-color:var(--feedlog-blue-600);color:#ffffff}.badge-enhancement:hover{background-color:var(--feedlog-blue-700)}:host(.dark) .badge-enhancement{background-color:var(--feedlog-blue-500)}:host(.dark) .badge-enhancement:hover{background-color:var(--feedlog-blue-600)}.badge-destructive{background-color:var(--feedlog-destructive);color:#ffffff}.badge-destructive:hover{background-color:var(--feedlog-destructive-hover)}`;
6
+
7
+ const FeedlogBadge = class {
8
+ constructor(hostRef) {
9
+ index.registerInstance(this, hostRef);
10
+ /**
11
+ * Badge variant style
12
+ */
13
+ this.variant = 'default';
14
+ }
15
+ render() {
16
+ return (index.h("span", { key: '482a74feb8d254a75383c30c7b39790edd04a05b', class: `badge badge-${this.variant}` }, index.h("slot", { key: '8d2db3fe0c2bc58e17829db4de7d0233189bb101' })));
17
+ }
18
+ };
19
+ FeedlogBadge.style = feedlogBadgeCss();
20
+
21
+ exports.feedlog_badge = FeedlogBadge;
@@ -1,22 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-5lluu_3h.js');
4
-
5
- const feedlogBadgeCss = () => `:host{display:inline-block;--feedlog-badge-font-size:0.75rem;--feedlog-badge-font-weight:500;--feedlog-badge-padding-x:0.5rem;--feedlog-badge-padding-y:0.125rem;--feedlog-badge-border-radius:calc(0.625rem - 2px);--feedlog-blue-500:oklch(0.623 0.214 259.815);--feedlog-blue-600:oklch(0.546 0.245 262.881);--feedlog-blue-700:oklch(0.488 0.243 264.376);--feedlog-destructive:#d4183d;--feedlog-destructive-hover:#b91c1c}:host(.dark){--feedlog-blue-500:oklch(0.623 0.214 259.815);--feedlog-blue-600:oklch(0.546 0.245 262.881);--feedlog-destructive:oklch(0.396 0.141 25.723);--feedlog-destructive-hover:oklch(0.45 0.16 25.723)}.badge{display:inline-flex;align-items:center;justify-content:center;font-size:var(--feedlog-badge-font-size);font-weight:var(--feedlog-badge-font-weight);padding:var(--feedlog-badge-padding-y) var(--feedlog-badge-padding-x);border-radius:var(--feedlog-badge-border-radius);white-space:nowrap;transition:background-color 0.15s ease;font-family:inherit}.badge-default{background-color:var(--feedlog-blue-600);color:#ffffff}.badge-default:hover{background-color:var(--feedlog-blue-700)}:host(.dark) .badge-default{background-color:var(--feedlog-blue-500)}:host(.dark) .badge-default:hover{background-color:var(--feedlog-blue-600)}.badge-enhancement{background-color:var(--feedlog-blue-600);color:#ffffff}.badge-enhancement:hover{background-color:var(--feedlog-blue-700)}:host(.dark) .badge-enhancement{background-color:var(--feedlog-blue-500)}:host(.dark) .badge-enhancement:hover{background-color:var(--feedlog-blue-600)}.badge-destructive{background-color:var(--feedlog-destructive);color:#ffffff}.badge-destructive:hover{background-color:var(--feedlog-destructive-hover)}`;
6
-
7
- const FeedlogBadge = class {
8
- constructor(hostRef) {
9
- index.registerInstance(this, hostRef);
10
- /**
11
- * Badge variant style
12
- */
13
- this.variant = 'default';
14
- }
15
- render() {
16
- return (index.h("span", { key: '482a74feb8d254a75383c30c7b39790edd04a05b', class: `badge badge-${this.variant}` }, index.h("slot", { key: '8d2db3fe0c2bc58e17829db4de7d0233189bb101' })));
17
- }
18
- };
19
- FeedlogBadge.style = feedlogBadgeCss();
3
+ var index = require('./index-DnsqTPII.js');
20
4
 
21
5
  const feedlogButtonCss = () => `:host{display:inline-block;font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;--feedlog-background:#ffffff;--feedlog-foreground:oklch(0.145 0 0);--feedlog-primary:#030213;--feedlog-primary-foreground:oklch(1 0 0);--feedlog-secondary:oklch(0.95 0.0058 264.53);--feedlog-secondary-foreground:#030213;--feedlog-muted:#ececf0;--feedlog-accent:#e9ebef;--feedlog-accent-foreground:#030213;--feedlog-border:rgba(0, 0, 0, 0.1);--feedlog-input:transparent;--feedlog-ring:oklch(0.708 0 0);--feedlog-destructive:#d4183d;--feedlog-radius:0.625rem}:host(.dark){--feedlog-background:oklch(0.145 0 0);--feedlog-foreground:oklch(0.985 0 0);--feedlog-primary:oklch(0.985 0 0);--feedlog-primary-foreground:oklch(0.205 0 0);--feedlog-secondary:oklch(0.269 0 0);--feedlog-secondary-foreground:oklch(0.985 0 0);--feedlog-muted:oklch(0.269 0 0);--feedlog-accent:oklch(0.269 0 0);--feedlog-accent-foreground:oklch(0.985 0 0);--feedlog-border:oklch(0.269 0 0);--feedlog-input:oklch(0.269 0 0);--feedlog-ring:oklch(0.439 0 0);--feedlog-destructive:oklch(0.396 0.141 25.723)}button{display:inline-flex;align-items:center;justify-content:center;gap:0.5rem;white-space:nowrap;font-size:0.875rem;font-weight:500;border-radius:calc(var(--feedlog-radius) - 2px);border:1px solid transparent;cursor:pointer;transition:all 0.15s ease;outline:none;font-family:inherit}button:disabled{opacity:0.5;cursor:not-allowed;pointer-events:none}button:focus-visible{border-color:var(--feedlog-ring);box-shadow:0 0 0 3px color-mix(in oklab, var(--feedlog-ring) 50%, transparent)}.button-default{background-color:var(--feedlog-primary);color:var(--feedlog-primary-foreground);border-color:transparent}.button-default:hover:not(:disabled){background-color:color-mix(in oklab, var(--feedlog-primary) 90%, transparent)}.button-outline{background-color:var(--feedlog-background);color:var(--feedlog-foreground);border-color:var(--feedlog-border)}.button-outline:hover:not(:disabled){background-color:var(--feedlog-accent);color:var(--feedlog-accent-foreground)}:host(.dark) .button-outline{background-color:transparent;border-color:var(--feedlog-input)}:host(.dark) .button-outline:hover:not(:disabled){background-color:color-mix(in oklab, var(--feedlog-accent) 50%, transparent)}.button-ghost{background-color:transparent;color:var(--feedlog-foreground);border-color:transparent}.button-ghost:hover:not(:disabled){background-color:var(--feedlog-accent);color:var(--feedlog-accent-foreground)}.button-destructive{background-color:var(--feedlog-destructive);color:#ffffff;border-color:transparent}.button-destructive:hover:not(:disabled){background-color:color-mix(in oklab, var(--feedlog-destructive) 90%, transparent)}:host(.dark) .button-destructive{background-color:color-mix(in oklab, var(--feedlog-destructive) 60%, transparent)}.button-size-sm{height:2rem;padding:0 0.75rem;font-size:0.75rem}.button-size-sm:has(svg:only-child){width:2rem;padding:0}.button-size-default{height:2.25rem;padding:0.5rem 1rem}.button-size-lg{height:2.5rem;padding:0.625rem 1.5rem;font-size:1rem}button svg{pointer-events:none;flex-shrink:0;width:1rem;height:1rem}`;
22
6
 
@@ -108,11 +92,10 @@ const FeedlogIssueComponent = class {
108
92
  const { issue } = this;
109
93
  if (!issue)
110
94
  return null;
111
- return (index.h(index.Host, { class: this.theme === 'dark' ? 'dark' : '' }, index.h("div", { class: "issue-card" }, index.h("div", { class: "issue-content" }, index.h("div", { class: "issue-header" }, index.h("div", { class: "issue-type-badge" }, issue.type === 'bug' ? (index.h("feedlog-badge", { variant: "destructive" }, "Bug")) : (index.h("feedlog-badge", { variant: "enhancement" }, "Enhancement"))), issue.pinnedAt && (index.h("div", { class: "pinned-indicator", title: "Pinned issue" }, "\uD83D\uDCCC"))), index.h("div", { class: "issue-main" }, index.h("div", { class: "issue-details" }, index.h("h3", { class: "issue-title" }, issue.title), index.h("p", { class: "issue-body" }, issue.body), index.h("div", { class: "issue-repository" }, index.h("span", { class: "repo-name" }, issue.repository.owner, "/", issue.repository.name))), issue.type !== 'bug' && (index.h("button", { class: `upvote-button ${issue.hasUpvoted ? 'upvoted' : ''}`, onClick: (e) => this.handleUpvote(e), title: issue.hasUpvoted ? 'Remove upvote' : 'Upvote this issue' }, issue.hasUpvoted ? index.h(HeartFilledIcon, null) : index.h(HeartOutlineIcon, null), index.h("span", { class: "upvote-count" }, issue.upvoteCount)))), index.h("div", { class: "issue-footer" }, index.h("span", { class: "issue-date", title: `Updated: ${issue.updatedAt}` }, "Updated ", this.formatDate(issue.updatedAt)), index.h("span", { class: "issue-date", title: `Created: ${issue.createdAt}` }, "Created ", this.formatDate(issue.createdAt)))))));
95
+ return (index.h(index.Host, { class: this.theme === 'dark' ? 'dark' : '' }, index.h("div", { class: "issue-card" }, index.h("div", { class: "issue-content" }, index.h("div", { class: "issue-header" }, index.h("div", { class: "issue-type-badge" }, issue.type === 'bug' ? (index.h("feedlog-badge", { variant: "destructive" }, "Bug")) : (index.h("feedlog-badge", { variant: "enhancement" }, "Enhancement"))), issue.pinnedAt && (index.h("div", { class: "pinned-indicator", title: "Pinned issue" }, "\uD83D\uDCCC"))), index.h("div", { class: "issue-main" }, index.h("div", { class: "issue-details" }, index.h("h3", { class: "issue-title" }, issue.title), index.h("p", { class: "issue-body" }, issue.body), index.h("div", { class: "issue-repository" }, index.h("span", { class: "repo-name" }, issue.repository.owner, "/", issue.repository.name))), issue.type !== 'bug' && (index.h("button", { class: `upvote-button ${issue.hasUpvoted ? 'upvoted' : ''}`, onClick: (e) => this.handleUpvote(e), title: issue.hasUpvoted ? 'Remove upvote' : 'Upvote this issue' }, issue.hasUpvoted ? HeartFilledIcon() : HeartOutlineIcon(), index.h("span", { class: "upvote-count" }, issue.upvoteCount)))), index.h("div", { class: "issue-footer" }, index.h("span", { class: "issue-date", title: `Updated: ${issue.updatedAt}` }, "Updated ", this.formatDate(issue.updatedAt)), index.h("span", { class: "issue-date", title: `Created: ${issue.createdAt}` }, "Created ", this.formatDate(issue.createdAt)))))));
112
96
  }
113
97
  };
114
98
  FeedlogIssueComponent.style = feedlogIssueCss();
115
99
 
116
- exports.feedlog_badge = FeedlogBadge;
117
100
  exports.feedlog_button = FeedlogButton;
118
101
  exports.feedlog_issue = FeedlogIssueComponent;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-5lluu_3h.js');
3
+ var index = require('./index-DnsqTPII.js');
4
4
 
5
5
  const feedlogCardCss = () => `:host{display:block;font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;--feedlog-card:#ffffff;--feedlog-card-foreground:oklch(0.145 0 0);--feedlog-border:rgba(0, 0, 0, 0.1);--feedlog-radius:0.625rem;--feedlog-card-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);--feedlog-card-hover-shadow:0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)}:host(.dark){--feedlog-card:oklch(0.145 0 0);--feedlog-card-foreground:oklch(0.985 0 0);--feedlog-border:oklch(0.269 0 0);--feedlog-card-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -2px rgba(0, 0, 0, 0.3);--feedlog-card-hover-shadow:0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -4px rgba(0, 0, 0, 0.3)}.feedlog-card{background-color:var(--feedlog-card);color:var(--feedlog-card-foreground);border:1px solid var(--feedlog-border);border-radius:var(--feedlog-radius);box-shadow:var(--feedlog-card-shadow);transition:box-shadow 0.15s ease;position:relative}.feedlog-card:hover{box-shadow:var(--feedlog-card-hover-shadow)}::slotted([slot="header"]){display:block}::slotted([slot="content"]){display:block}::slotted([slot="footer"]){display:block}`;
6
6
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-5lluu_3h.js');
3
+ var index = require('./index-DnsqTPII.js');
4
4
 
5
5
  /**
6
6
  * HTML and XSS sanitization utilities
@@ -354,16 +354,29 @@ const FeedlogGithubIssuesClient = class {
354
354
  this.hasMore = false;
355
355
  this.isLoadingMore = false;
356
356
  this.sdk = null;
357
+ /** Counter to track fetch operations and prevent stale updates */
358
+ this.fetchRequestId = 0;
359
+ /** Flag to prevent state updates after component disconnect */
360
+ this.isDisconnected = false;
361
+ /** Map to track the latest upvote request ID for each issue to handle race conditions */
362
+ this.upvoteRequestIds = new Map();
357
363
  this.handleUpvote = async (event) => {
358
- if (!this.sdk) {
364
+ if (!this.sdk || this.isDisconnected) {
359
365
  return;
360
366
  }
361
367
  const { issueId, currentUpvoted, currentCount } = event.detail;
368
+ // Track request to handle race conditions
369
+ const requestId = (this.upvoteRequestIds.get(issueId) || 0) + 1;
370
+ this.upvoteRequestIds.set(issueId, requestId);
362
371
  // Optimistic update
363
372
  this.issues = this.issues.map(issue => issue.id === issueId
364
373
  ? Object.assign(Object.assign({}, issue), { hasUpvoted: !currentUpvoted, upvoteCount: currentUpvoted ? currentCount - 1 : currentCount + 1 }) : issue);
365
374
  try {
366
375
  const result = await this.sdk.toggleUpvote(issueId);
376
+ // Ignore if component disconnected or request is stale
377
+ if (this.isDisconnected || this.upvoteRequestIds.get(issueId) !== requestId) {
378
+ return;
379
+ }
367
380
  // Update with server response
368
381
  this.issues = this.issues.map(issue => issue.id === issueId
369
382
  ? Object.assign(Object.assign({}, issue), { hasUpvoted: result.upvoted, upvoteCount: result.upvoteCount }) : issue);
@@ -374,6 +387,10 @@ const FeedlogGithubIssuesClient = class {
374
387
  });
375
388
  }
376
389
  catch (err) {
390
+ // Ignore if component disconnected or request is stale
391
+ if (this.isDisconnected || this.upvoteRequestIds.get(issueId) !== requestId) {
392
+ return;
393
+ }
377
394
  // Revert optimistic update on error
378
395
  this.issues = this.issues.map(issue => issue.id === issueId
379
396
  ? Object.assign(Object.assign({}, issue), { hasUpvoted: currentUpvoted, upvoteCount: currentCount }) : issue);
@@ -388,11 +405,18 @@ const FeedlogGithubIssuesClient = class {
388
405
  this.initializeSDK();
389
406
  this.fetchIssues();
390
407
  }
408
+ disconnectedCallback() {
409
+ // Prevent any pending async operations from updating state
410
+ this.isDisconnected = true;
411
+ this.fetchRequestId++;
412
+ }
391
413
  componentDidUpdate() {
392
414
  // Re-fetch if any props changed
393
415
  const typeChanged = this.previousType !== this.type;
394
416
  const limitChanged = this.previousLimit !== this.limit;
395
417
  if (typeChanged || limitChanged) {
418
+ // Invalidate any in-flight requests
419
+ this.fetchRequestId++;
396
420
  // Reset pagination when filters change
397
421
  this.cursor = null;
398
422
  this.hasMore = false;
@@ -420,6 +444,8 @@ const FeedlogGithubIssuesClient = class {
420
444
  if (!this.sdk) {
421
445
  return;
422
446
  }
447
+ // Capture current request ID to detect stale responses
448
+ const currentRequestId = this.fetchRequestId;
423
449
  try {
424
450
  this.loading = true;
425
451
  this.error = null;
@@ -434,11 +460,19 @@ const FeedlogGithubIssuesClient = class {
434
460
  params.cursor = this.cursor;
435
461
  }
436
462
  const response = await this.sdk.fetchIssues(params);
463
+ // Ignore response if component disconnected or a newer request was made
464
+ if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
465
+ return;
466
+ }
437
467
  this.issues = response.issues;
438
468
  this.cursor = response.pagination.cursor;
439
469
  this.hasMore = response.pagination.hasMore;
440
470
  }
441
471
  catch (err) {
472
+ // Ignore errors from stale requests
473
+ if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
474
+ return;
475
+ }
442
476
  const errorMsg = err instanceof Error ? err.message : 'Failed to fetch issues';
443
477
  this.error = errorMsg;
444
478
  this.issues = [];
@@ -448,14 +482,19 @@ const FeedlogGithubIssuesClient = class {
448
482
  });
449
483
  }
450
484
  finally {
451
- this.loading = false;
452
- this.isLoadingMore = false;
485
+ // Only update loading state if this is still the current request
486
+ if (!this.isDisconnected && currentRequestId === this.fetchRequestId) {
487
+ this.loading = false;
488
+ this.isLoadingMore = false;
489
+ }
453
490
  }
454
491
  }
455
492
  async loadMore() {
456
493
  if (!this.sdk || !this.hasMore || this.isLoadingMore || this.loading) {
457
494
  return;
458
495
  }
496
+ // Capture current request ID to detect stale responses
497
+ const currentRequestId = this.fetchRequestId;
459
498
  this.isLoadingMore = true;
460
499
  try {
461
500
  const params = {};
@@ -469,11 +508,19 @@ const FeedlogGithubIssuesClient = class {
469
508
  params.cursor = this.cursor;
470
509
  }
471
510
  const response = await this.sdk.fetchIssues(params);
511
+ // Ignore response if component disconnected or a newer request was made
512
+ if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
513
+ return;
514
+ }
472
515
  this.issues = [...this.issues, ...response.issues];
473
516
  this.cursor = response.pagination.cursor;
474
517
  this.hasMore = response.pagination.hasMore;
475
518
  }
476
519
  catch (err) {
520
+ // Ignore errors from stale requests
521
+ if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
522
+ return;
523
+ }
477
524
  const errorMsg = err instanceof Error ? err.message : 'Failed to load more issues';
478
525
  this.feedlogError.emit({
479
526
  error: errorMsg,
@@ -481,11 +528,14 @@ const FeedlogGithubIssuesClient = class {
481
528
  });
482
529
  }
483
530
  finally {
484
- this.isLoadingMore = false;
531
+ // Only update loading state if this is still the current request
532
+ if (!this.isDisconnected && currentRequestId === this.fetchRequestId) {
533
+ this.isLoadingMore = false;
534
+ }
485
535
  }
486
536
  }
487
537
  render() {
488
- return (index.h("feedlog-github-issues", { key: '9b12fc472a905e7117b383ccdf2c0a995f480366', issues: this.issues, maxWidth: this.maxWidth, theme: this.theme, heading: this.heading, subtitle: this.subtitle, loading: this.loading, error: this.error, hasMore: this.hasMore, isLoadingMore: this.isLoadingMore, onFeedlogUpvote: this.handleUpvote, onFeedlogLoadMore: async () => this.loadMore() }));
538
+ return (index.h("feedlog-github-issues", { key: 'bb63dd29e99dfd9418b6cb12a1f45dff086378ab', issues: this.issues, maxWidth: this.maxWidth, theme: this.theme, heading: this.heading, subtitle: this.subtitle, loading: this.loading, error: this.error, hasMore: this.hasMore, isLoadingMore: this.isLoadingMore, onFeedlogUpvote: this.handleUpvote, onFeedlogLoadMore: async () => this.loadMore() }));
489
539
  }
490
540
  };
491
541
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-5lluu_3h.js');
3
+ var index = require('./index-DnsqTPII.js');
4
4
 
5
5
  const feedlogGithubIssuesCss = () => `:host{display:block;font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;--feedlog-background:#ffffff;--feedlog-foreground:oklch(0.145 0 0);--feedlog-card:#ffffff;--feedlog-card-foreground:oklch(0.145 0 0);--feedlog-muted:#ececf0;--feedlog-muted-foreground:#717182;--feedlog-border:rgba(0, 0, 0, 0.1);--feedlog-accent-color:#2563eb;--feedlog-destructive:#d4183d;--feedlog-blue-400:oklch(0.707 0.165 254.624);--feedlog-blue-600:oklch(0.546 0.245 262.881);--feedlog-blue-100:oklch(0.932 0.032 255.585);--feedlog-red-100:#fce7f3;--feedlog-red-400:#f472b6;--feedlog-red-600:#db2777;--feedlog-radius:0.625rem;--feedlog-gap:0.5rem;--feedlog-padding:2rem}:host(.dark){--feedlog-background:oklch(0.145 0 0);--feedlog-foreground:oklch(0.985 0 0);--feedlog-card:oklch(0.145 0 0);--feedlog-card-foreground:oklch(0.985 0 0);--feedlog-muted:oklch(0.269 0 0);--feedlog-muted-foreground:oklch(0.708 0 0);--feedlog-border:oklch(0.269 0 0);--feedlog-accent-color:#3b82f6;--feedlog-destructive:oklch(0.396 0.141 25.723);--feedlog-blue-400:oklch(0.707 0.165 254.624);--feedlog-blue-600:oklch(0.546 0.245 262.881);--feedlog-blue-900-30:color-mix(in oklab, oklch(0.379 0.146 265.522) 30%, transparent);--feedlog-red-900-30:color-mix(in oklab, oklch(0.396 0.141 25.723) 30%, transparent)}.github-issues-container{min-height:100vh;background-color:var(--feedlog-background);padding:var(--feedlog-padding);margin:0 auto}.issues-header{margin-bottom:1.5rem;display:flex;align-items:flex-start;justify-content:space-between}.header-content{flex:1}.issues-title{color:var(--feedlog-foreground);margin:0 0 0.25rem 0;font-size:1.5rem;font-weight:500;line-height:1.5}.issues-subtitle{color:var(--feedlog-muted-foreground);font-size:0.875rem;margin:0}.loading-state,.error-state{padding:2rem;text-align:center;color:var(--feedlog-muted-foreground)}.error-state{color:var(--feedlog-destructive)}.issues-list{display:flex;flex-direction:column;gap:var(--feedlog-gap)}.empty-state{text-align:center;padding:3rem 1.5rem;color:var(--feedlog-muted-foreground);font-size:0.875rem}.load-more-container{display:flex;justify-content:center;padding:2rem 0;gap:1rem}`;
6
6
 
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ var index = require('./index-DnsqTPII.js');
4
+
5
+ const feedlogIssuesListCss = () => `:host{display:block;font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;--feedlog-background:#ffffff;--feedlog-foreground:oklch(0.145 0 0);--feedlog-card:#ffffff;--feedlog-card-foreground:oklch(0.145 0 0);--feedlog-muted:#ececf0;--feedlog-muted-foreground:#717182;--feedlog-border:rgba(0, 0, 0, 0.1);--feedlog-accent-color:#2563eb;--feedlog-destructive:#d4183d;--feedlog-blue-400:oklch(0.707 0.165 254.624);--feedlog-blue-600:oklch(0.546 0.245 262.881);--feedlog-blue-100:oklch(0.932 0.032 255.585);--feedlog-red-100:#fce7f3;--feedlog-red-400:#f472b6;--feedlog-red-600:#db2777;--feedlog-radius:0.625rem;--feedlog-gap:0.5rem}:host(.dark){--feedlog-background:oklch(0.145 0 0);--feedlog-foreground:oklch(0.985 0 0);--feedlog-card:oklch(0.145 0 0);--feedlog-card-foreground:oklch(0.985 0 0);--feedlog-muted:oklch(0.269 0 0);--feedlog-muted-foreground:oklch(0.708 0 0);--feedlog-border:oklch(0.269 0 0);--feedlog-accent-color:#3b82f6;--feedlog-destructive:oklch(0.396 0.141 25.723);--feedlog-blue-400:oklch(0.707 0.165 254.624);--feedlog-blue-600:oklch(0.546 0.245 262.881);--feedlog-blue-900-30:color-mix(in oklab, oklch(0.379 0.146 265.522) 30%, transparent);--feedlog-red-900-30:color-mix(in oklab, oklch(0.396 0.141 25.723) 30%, transparent)}.issues-list{display:flex;flex-direction:column;gap:var(--feedlog-gap)}.empty-state{text-align:center;padding:3rem 1.5rem;color:var(--feedlog-muted-foreground);font-size:0.875rem}.issue-card{background-color:var(--feedlog-card);border:1px solid var(--feedlog-border);border-radius:var(--feedlog-radius);box-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);transition:box-shadow 0.15s ease;position:relative}.issue-card:hover{box-shadow:0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)}.issue-content{padding:1.5rem;display:flex;flex-direction:column;gap:1rem}.issue-header{display:flex;align-items:center;gap:0.5rem;justify-content:space-between}.issue-type-badge{width:fit-content}.pinned-indicator{font-size:1rem;opacity:0.7}.issue-main{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.issue-details{flex:1;min-width:0}.issue-title{margin:0 0 0.5rem 0;font-size:1rem;font-weight:600;color:var(--feedlog-card-foreground);line-height:1.5;word-wrap:break-word}.issue-body{margin:0 0 0.75rem 0;font-size:0.875rem;color:var(--feedlog-muted-foreground);line-height:1.5;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;word-wrap:break-word}.issue-repository{display:flex;align-items:center;gap:0.5rem;font-size:0.75rem;color:var(--feedlog-muted-foreground)}.repo-name{font-weight:500}.github-number{background-color:var(--feedlog-muted);padding:0.125rem 0.375rem;border-radius:0.25rem;font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace}.upvote-button{display:flex;align-items:center;gap:0.375rem;padding:0.5rem 0.75rem;background-color:var(--feedlog-muted);border:1px solid var(--feedlog-border);border-radius:var(--feedlog-radius);color:var(--feedlog-muted-foreground);font-size:0.875rem;font-weight:500;cursor:pointer;transition:all 0.15s ease;white-space:nowrap}.upvote-button:hover{background-color:var(--feedlog-accent-color);color:white}.upvote-button.upvoted{background-color:var(--feedlog-destructive);color:white;border-color:var(--feedlog-destructive)}.upvote-button.upvoted:hover{background-color:var(--feedlog-red-600);border-color:var(--feedlog-red-600)}.upvote-icon{width:1rem;height:1rem;flex-shrink:0}.upvote-icon.filled{color:currentColor}.upvote-icon.outline{stroke:currentColor}.upvote-count{font-variant-numeric:tabular-nums}.issue-footer{display:flex;align-items:center;gap:1rem;font-size:0.75rem;color:var(--feedlog-muted-foreground)}.issue-date{white-space:nowrap}@media (max-width: 640px){.issue-content{padding:1rem;gap:0.75rem}.issue-main{flex-direction:column;align-items:stretch;gap:0.75rem}.upvote-button{align-self:flex-start}.issue-footer{flex-direction:column;align-items:flex-start;gap:0.25rem}}`;
6
+
7
+ /**
8
+ * Heart icon SVG component (filled)
9
+ */
10
+ const HeartFilledIcon = () => (index.h("svg", { class: "upvote-icon filled", xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none" }, index.h("path", { d: "M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" })));
11
+ /**
12
+ * Heart icon SVG component (outline)
13
+ */
14
+ const HeartOutlineIcon = () => (index.h("svg", { class: "upvote-icon outline", xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("path", { d: "M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" })));
15
+ const FeedlogIssuesList = class {
16
+ constructor(hostRef) {
17
+ index.registerInstance(this, hostRef);
18
+ this.feedlogUpvote = index.createEvent(this, "feedlogUpvote");
19
+ /**
20
+ * Array of issues to display
21
+ */
22
+ this.issues = [];
23
+ /**
24
+ * Theme variant: 'light' or 'dark'
25
+ */
26
+ this.theme = 'light';
27
+ this.handleUpvote = (event, issue) => {
28
+ event.stopPropagation();
29
+ this.feedlogUpvote.emit({
30
+ issueId: issue.id,
31
+ currentUpvoted: issue.hasUpvoted,
32
+ currentCount: issue.upvoteCount,
33
+ });
34
+ };
35
+ }
36
+ /**
37
+ * Format an ISO date string to a relative time string
38
+ */
39
+ formatDate(dateString) {
40
+ try {
41
+ const date = new Date(dateString);
42
+ const now = new Date();
43
+ const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
44
+ if (seconds < 60)
45
+ return 'just now';
46
+ if (seconds < 3600)
47
+ return `${Math.floor(seconds / 60)}m ago`;
48
+ if (seconds < 86400)
49
+ return `${Math.floor(seconds / 3600)}h ago`;
50
+ if (seconds < 604800)
51
+ return `${Math.floor(seconds / 86400)}d ago`;
52
+ if (seconds < 2592000)
53
+ return `${Math.floor(seconds / 604800)}w ago`;
54
+ return date.toLocaleDateString();
55
+ }
56
+ catch (_a) {
57
+ return 'unknown date';
58
+ }
59
+ }
60
+ render() {
61
+ return (index.h(index.Host, { key: 'eece99cd1096c6f0407e196b34e30b4d249bc3fa', class: this.theme === 'dark' ? 'dark' : '' }, index.h("div", { key: 'e42daf5d526276290687125b47fb1fc9e11af518', class: "issues-list" }, this.issues.length === 0 ? (index.h("div", { class: "empty-state" }, index.h("p", null, "No issues found"))) : (this.issues.map(issue => (index.h("div", { key: issue.id, class: "issue-card" }, index.h("div", { class: "issue-content" }, index.h("div", { class: "issue-header" }, index.h("div", { class: "issue-type-badge" }, issue.type === 'bug' ? (index.h("feedlog-badge", { variant: "destructive" }, "Bug")) : (index.h("feedlog-badge", { variant: "enhancement" }, "Enhancement"))), issue.pinnedAt && (index.h("div", { class: "pinned-indicator", title: "Pinned issue" }, "\uD83D\uDCCC"))), index.h("div", { class: "issue-main" }, index.h("div", { class: "issue-details" }, index.h("h3", { class: "issue-title" }, issue.title), index.h("p", { class: "issue-body" }, issue.body), index.h("div", { class: "issue-repository" }, index.h("span", { class: "repo-name" }, issue.repository.owner, "/", issue.repository.name))), index.h("button", { class: `upvote-button ${issue.hasUpvoted ? 'upvoted' : ''}`, onClick: e => this.handleUpvote(e, issue), title: issue.hasUpvoted ? 'Remove upvote' : 'Upvote this issue' }, issue.hasUpvoted ? HeartFilledIcon() : HeartOutlineIcon(), index.h("span", { class: "upvote-count" }, issue.upvoteCount))), index.h("div", { class: "issue-footer" }, index.h("span", { class: "issue-date", title: `Updated: ${issue.updatedAt}` }, "Updated ", this.formatDate(issue.updatedAt)), index.h("span", { class: "issue-date", title: `Created: ${issue.createdAt}` }, "Created ", this.formatDate(issue.createdAt)))))))))));
62
+ }
63
+ };
64
+ FeedlogIssuesList.style = feedlogIssuesListCss();
65
+
66
+ exports.feedlog_issues_list = FeedlogIssuesList;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-5lluu_3h.js');
3
+ var index = require('./index-DnsqTPII.js');
4
4
 
5
5
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
6
6
  /*
@@ -18,7 +18,7 @@ var patchBrowser = () => {
18
18
 
19
19
  patchBrowser().then(async (options) => {
20
20
  await index.globalScripts();
21
- return index.bootstrapLazy([["feedlog-card.cjs",[[257,"feedlog-card"]]],["feedlog-badge_3.cjs",[[1,"feedlog-issue",{"issue":[16],"theme":[1]}],[257,"feedlog-button",{"variant":[1],"size":[1],"disabled":[4],"type":[1]}],[257,"feedlog-badge",{"variant":[1]}]]],["feedlog-github-issues.cjs",[[1,"feedlog-github-issues",{"issues":[16],"maxWidth":[1,"max-width"],"theme":[1025],"heading":[1],"subtitle":[1],"loading":[4],"error":[1],"hasMore":[4,"has-more"],"isLoadingMore":[4,"is-loading-more"],"currentTheme":[32]}]]],["feedlog-github-issues-client.cjs",[[1,"feedlog-github-issues-client",{"apiKey":[1,"api-key"],"type":[1],"limit":[2],"endpoint":[1],"maxWidth":[1,"max-width"],"theme":[1],"heading":[1],"subtitle":[1],"issues":[32],"loading":[32],"error":[32],"cursor":[32],"hasMore":[32],"isLoadingMore":[32]}]]]], options);
21
+ return index.bootstrapLazy([["feedlog-issues-list.cjs",[[1,"feedlog-issues-list",{"issues":[16],"theme":[1]}]]],["feedlog-card.cjs",[[257,"feedlog-card"]]],["feedlog-badge.cjs",[[257,"feedlog-badge",{"variant":[1]}]]],["feedlog-button_2.cjs",[[1,"feedlog-issue",{"issue":[16],"theme":[1]}],[257,"feedlog-button",{"variant":[1],"size":[1],"disabled":[4],"type":[1]}]]],["feedlog-github-issues.cjs",[[1,"feedlog-github-issues",{"issues":[16],"maxWidth":[1,"max-width"],"theme":[1025],"heading":[1],"subtitle":[1],"loading":[4],"error":[1],"hasMore":[4,"has-more"],"isLoadingMore":[4,"is-loading-more"],"currentTheme":[32]}]]],["feedlog-github-issues-client.cjs",[[1,"feedlog-github-issues-client",{"apiKey":[1,"api-key"],"type":[1],"limit":[2],"endpoint":[1],"maxWidth":[1,"max-width"],"theme":[1],"heading":[1],"subtitle":[1],"issues":[32],"loading":[32],"error":[32],"cursor":[32],"hasMore":[32],"isLoadingMore":[32]}]]]], options);
22
22
  });
23
23
 
24
24
  exports.setNonce = index.setNonce;
@@ -2042,13 +2042,6 @@ var h = (nodeName, vnodeData, ...children) => {
2042
2042
  }
2043
2043
  }
2044
2044
  }
2045
- if (typeof nodeName === "function") {
2046
- return nodeName(
2047
- vnodeData === null ? {} : vnodeData,
2048
- vNodeChildren,
2049
- vdomFnUtils
2050
- );
2051
- }
2052
2045
  const vnode = newVNode(nodeName, null);
2053
2046
  vnode.$attrs$ = vnodeData;
2054
2047
  if (vNodeChildren.length > 0) {
@@ -2077,36 +2070,6 @@ var newVNode = (tag, text) => {
2077
2070
  };
2078
2071
  var Host = {};
2079
2072
  var isHost = (node) => node && node.$tag$ === Host;
2080
- var vdomFnUtils = {
2081
- forEach: (children, cb) => children.map(convertToPublic).forEach(cb),
2082
- map: (children, cb) => children.map(convertToPublic).map(cb).map(convertToPrivate)
2083
- };
2084
- var convertToPublic = (node) => ({
2085
- vattrs: node.$attrs$,
2086
- vchildren: node.$children$,
2087
- vkey: node.$key$,
2088
- vname: node.$name$,
2089
- vtag: node.$tag$,
2090
- vtext: node.$text$
2091
- });
2092
- var convertToPrivate = (node) => {
2093
- if (typeof node.vtag === "function") {
2094
- const vnodeData = { ...node.vattrs };
2095
- if (node.vkey) {
2096
- vnodeData.key = node.vkey;
2097
- }
2098
- if (node.vname) {
2099
- vnodeData.name = node.vname;
2100
- }
2101
- return h(node.vtag, vnodeData, ...node.vchildren || []);
2102
- }
2103
- const vnode = newVNode(node.vtag, node.vtext);
2104
- vnode.$attrs$ = node.vattrs;
2105
- vnode.$children$ = node.vchildren;
2106
- vnode.$key$ = node.vkey;
2107
- vnode.$name$ = node.vname;
2108
- return vnode;
2109
- };
2110
2073
  var createSupportsRuleRe = (selector) => {
2111
2074
  const safeSelector2 = escapeRegExpSpecialCharacters(selector);
2112
2075
  return new RegExp(
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-5lluu_3h.js');
3
+ var index = require('./index-DnsqTPII.js');
4
4
 
5
5
  const defineCustomElements = async (win, options) => {
6
6
  if (typeof window === 'undefined') return undefined;
7
7
  await index.globalScripts();
8
- return index.bootstrapLazy([["feedlog-card.cjs",[[257,"feedlog-card"]]],["feedlog-badge_3.cjs",[[1,"feedlog-issue",{"issue":[16],"theme":[1]}],[257,"feedlog-button",{"variant":[1],"size":[1],"disabled":[4],"type":[1]}],[257,"feedlog-badge",{"variant":[1]}]]],["feedlog-github-issues.cjs",[[1,"feedlog-github-issues",{"issues":[16],"maxWidth":[1,"max-width"],"theme":[1025],"heading":[1],"subtitle":[1],"loading":[4],"error":[1],"hasMore":[4,"has-more"],"isLoadingMore":[4,"is-loading-more"],"currentTheme":[32]}]]],["feedlog-github-issues-client.cjs",[[1,"feedlog-github-issues-client",{"apiKey":[1,"api-key"],"type":[1],"limit":[2],"endpoint":[1],"maxWidth":[1,"max-width"],"theme":[1],"heading":[1],"subtitle":[1],"issues":[32],"loading":[32],"error":[32],"cursor":[32],"hasMore":[32],"isLoadingMore":[32]}]]]], options);
8
+ return index.bootstrapLazy([["feedlog-issues-list.cjs",[[1,"feedlog-issues-list",{"issues":[16],"theme":[1]}]]],["feedlog-card.cjs",[[257,"feedlog-card"]]],["feedlog-badge.cjs",[[257,"feedlog-badge",{"variant":[1]}]]],["feedlog-button_2.cjs",[[1,"feedlog-issue",{"issue":[16],"theme":[1]}],[257,"feedlog-button",{"variant":[1],"size":[1],"disabled":[4],"type":[1]}]]],["feedlog-github-issues.cjs",[[1,"feedlog-github-issues",{"issues":[16],"maxWidth":[1,"max-width"],"theme":[1025],"heading":[1],"subtitle":[1],"loading":[4],"error":[1],"hasMore":[4,"has-more"],"isLoadingMore":[4,"is-loading-more"],"currentTheme":[32]}]]],["feedlog-github-issues-client.cjs",[[1,"feedlog-github-issues-client",{"apiKey":[1,"api-key"],"type":[1],"limit":[2],"endpoint":[1],"maxWidth":[1,"max-width"],"theme":[1],"heading":[1],"subtitle":[1],"issues":[32],"loading":[32],"error":[32],"cursor":[32],"hasMore":[32],"isLoadingMore":[32]}]]]], options);
9
9
  };
10
10
 
11
11
  exports.setNonce = index.setNonce;
@@ -5,7 +5,8 @@
5
5
  "components/feedlog-button/feedlog-button.js",
6
6
  "components/feedlog-github-issues/feedlog-github-issues.js",
7
7
  "components/feedlog-github-issues-client/feedlog-github-issues-client.js",
8
- "components/feedlog-issue/feedlog-issue.js"
8
+ "components/feedlog-issue/feedlog-issue.js",
9
+ "components/feedlog-issues-list/feedlog-issues-list.js"
9
10
  ],
10
11
  "compiler": {
11
12
  "name": "@stencil/core",
@@ -23,16 +23,29 @@ export class FeedlogGithubIssuesClient {
23
23
  this.hasMore = false;
24
24
  this.isLoadingMore = false;
25
25
  this.sdk = null;
26
+ /** Counter to track fetch operations and prevent stale updates */
27
+ this.fetchRequestId = 0;
28
+ /** Flag to prevent state updates after component disconnect */
29
+ this.isDisconnected = false;
30
+ /** Map to track the latest upvote request ID for each issue to handle race conditions */
31
+ this.upvoteRequestIds = new Map();
26
32
  this.handleUpvote = async (event) => {
27
- if (!this.sdk) {
33
+ if (!this.sdk || this.isDisconnected) {
28
34
  return;
29
35
  }
30
36
  const { issueId, currentUpvoted, currentCount } = event.detail;
37
+ // Track request to handle race conditions
38
+ const requestId = (this.upvoteRequestIds.get(issueId) || 0) + 1;
39
+ this.upvoteRequestIds.set(issueId, requestId);
31
40
  // Optimistic update
32
41
  this.issues = this.issues.map(issue => issue.id === issueId
33
42
  ? Object.assign(Object.assign({}, issue), { hasUpvoted: !currentUpvoted, upvoteCount: currentUpvoted ? currentCount - 1 : currentCount + 1 }) : issue);
34
43
  try {
35
44
  const result = await this.sdk.toggleUpvote(issueId);
45
+ // Ignore if component disconnected or request is stale
46
+ if (this.isDisconnected || this.upvoteRequestIds.get(issueId) !== requestId) {
47
+ return;
48
+ }
36
49
  // Update with server response
37
50
  this.issues = this.issues.map(issue => issue.id === issueId
38
51
  ? Object.assign(Object.assign({}, issue), { hasUpvoted: result.upvoted, upvoteCount: result.upvoteCount }) : issue);
@@ -43,6 +56,10 @@ export class FeedlogGithubIssuesClient {
43
56
  });
44
57
  }
45
58
  catch (err) {
59
+ // Ignore if component disconnected or request is stale
60
+ if (this.isDisconnected || this.upvoteRequestIds.get(issueId) !== requestId) {
61
+ return;
62
+ }
46
63
  // Revert optimistic update on error
47
64
  this.issues = this.issues.map(issue => issue.id === issueId
48
65
  ? Object.assign(Object.assign({}, issue), { hasUpvoted: currentUpvoted, upvoteCount: currentCount }) : issue);
@@ -57,11 +74,18 @@ export class FeedlogGithubIssuesClient {
57
74
  this.initializeSDK();
58
75
  this.fetchIssues();
59
76
  }
77
+ disconnectedCallback() {
78
+ // Prevent any pending async operations from updating state
79
+ this.isDisconnected = true;
80
+ this.fetchRequestId++;
81
+ }
60
82
  componentDidUpdate() {
61
83
  // Re-fetch if any props changed
62
84
  const typeChanged = this.previousType !== this.type;
63
85
  const limitChanged = this.previousLimit !== this.limit;
64
86
  if (typeChanged || limitChanged) {
87
+ // Invalidate any in-flight requests
88
+ this.fetchRequestId++;
65
89
  // Reset pagination when filters change
66
90
  this.cursor = null;
67
91
  this.hasMore = false;
@@ -89,6 +113,8 @@ export class FeedlogGithubIssuesClient {
89
113
  if (!this.sdk) {
90
114
  return;
91
115
  }
116
+ // Capture current request ID to detect stale responses
117
+ const currentRequestId = this.fetchRequestId;
92
118
  try {
93
119
  this.loading = true;
94
120
  this.error = null;
@@ -103,11 +129,19 @@ export class FeedlogGithubIssuesClient {
103
129
  params.cursor = this.cursor;
104
130
  }
105
131
  const response = await this.sdk.fetchIssues(params);
132
+ // Ignore response if component disconnected or a newer request was made
133
+ if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
134
+ return;
135
+ }
106
136
  this.issues = response.issues;
107
137
  this.cursor = response.pagination.cursor;
108
138
  this.hasMore = response.pagination.hasMore;
109
139
  }
110
140
  catch (err) {
141
+ // Ignore errors from stale requests
142
+ if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
143
+ return;
144
+ }
111
145
  const errorMsg = err instanceof Error ? err.message : 'Failed to fetch issues';
112
146
  this.error = errorMsg;
113
147
  this.issues = [];
@@ -117,14 +151,19 @@ export class FeedlogGithubIssuesClient {
117
151
  });
118
152
  }
119
153
  finally {
120
- this.loading = false;
121
- this.isLoadingMore = false;
154
+ // Only update loading state if this is still the current request
155
+ if (!this.isDisconnected && currentRequestId === this.fetchRequestId) {
156
+ this.loading = false;
157
+ this.isLoadingMore = false;
158
+ }
122
159
  }
123
160
  }
124
161
  async loadMore() {
125
162
  if (!this.sdk || !this.hasMore || this.isLoadingMore || this.loading) {
126
163
  return;
127
164
  }
165
+ // Capture current request ID to detect stale responses
166
+ const currentRequestId = this.fetchRequestId;
128
167
  this.isLoadingMore = true;
129
168
  try {
130
169
  const params = {};
@@ -138,11 +177,19 @@ export class FeedlogGithubIssuesClient {
138
177
  params.cursor = this.cursor;
139
178
  }
140
179
  const response = await this.sdk.fetchIssues(params);
180
+ // Ignore response if component disconnected or a newer request was made
181
+ if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
182
+ return;
183
+ }
141
184
  this.issues = [...this.issues, ...response.issues];
142
185
  this.cursor = response.pagination.cursor;
143
186
  this.hasMore = response.pagination.hasMore;
144
187
  }
145
188
  catch (err) {
189
+ // Ignore errors from stale requests
190
+ if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
191
+ return;
192
+ }
146
193
  const errorMsg = err instanceof Error ? err.message : 'Failed to load more issues';
147
194
  this.feedlogError.emit({
148
195
  error: errorMsg,
@@ -150,11 +197,14 @@ export class FeedlogGithubIssuesClient {
150
197
  });
151
198
  }
152
199
  finally {
153
- this.isLoadingMore = false;
200
+ // Only update loading state if this is still the current request
201
+ if (!this.isDisconnected && currentRequestId === this.fetchRequestId) {
202
+ this.isLoadingMore = false;
203
+ }
154
204
  }
155
205
  }
156
206
  render() {
157
- return (h("feedlog-github-issues", { key: '9b12fc472a905e7117b383ccdf2c0a995f480366', issues: this.issues, maxWidth: this.maxWidth, theme: this.theme, heading: this.heading, subtitle: this.subtitle, loading: this.loading, error: this.error, hasMore: this.hasMore, isLoadingMore: this.isLoadingMore, onFeedlogUpvote: this.handleUpvote, onFeedlogLoadMore: async () => this.loadMore() }));
207
+ return (h("feedlog-github-issues", { key: 'bb63dd29e99dfd9418b6cb12a1f45dff086378ab', issues: this.issues, maxWidth: this.maxWidth, theme: this.theme, heading: this.heading, subtitle: this.subtitle, loading: this.loading, error: this.error, hasMore: this.hasMore, isLoadingMore: this.isLoadingMore, onFeedlogUpvote: this.handleUpvote, onFeedlogLoadMore: async () => this.loadMore() }));
158
208
  }
159
209
  static get is() { return "feedlog-github-issues-client"; }
160
210
  static get encapsulation() { return "shadow"; }
@@ -55,7 +55,7 @@ export class FeedlogIssueComponent {
55
55
  const { issue } = this;
56
56
  if (!issue)
57
57
  return null;
58
- return (h(Host, { class: this.theme === 'dark' ? 'dark' : '' }, h("div", { class: "issue-card" }, h("div", { class: "issue-content" }, h("div", { class: "issue-header" }, h("div", { class: "issue-type-badge" }, issue.type === 'bug' ? (h("feedlog-badge", { variant: "destructive" }, "Bug")) : (h("feedlog-badge", { variant: "enhancement" }, "Enhancement"))), issue.pinnedAt && (h("div", { class: "pinned-indicator", title: "Pinned issue" }, "\uD83D\uDCCC"))), h("div", { class: "issue-main" }, h("div", { class: "issue-details" }, h("h3", { class: "issue-title" }, issue.title), h("p", { class: "issue-body" }, issue.body), h("div", { class: "issue-repository" }, h("span", { class: "repo-name" }, issue.repository.owner, "/", issue.repository.name))), issue.type !== 'bug' && (h("button", { class: `upvote-button ${issue.hasUpvoted ? 'upvoted' : ''}`, onClick: (e) => this.handleUpvote(e), title: issue.hasUpvoted ? 'Remove upvote' : 'Upvote this issue' }, issue.hasUpvoted ? h(HeartFilledIcon, null) : h(HeartOutlineIcon, null), h("span", { class: "upvote-count" }, issue.upvoteCount)))), h("div", { class: "issue-footer" }, h("span", { class: "issue-date", title: `Updated: ${issue.updatedAt}` }, "Updated ", this.formatDate(issue.updatedAt)), h("span", { class: "issue-date", title: `Created: ${issue.createdAt}` }, "Created ", this.formatDate(issue.createdAt)))))));
58
+ return (h(Host, { class: this.theme === 'dark' ? 'dark' : '' }, h("div", { class: "issue-card" }, h("div", { class: "issue-content" }, h("div", { class: "issue-header" }, h("div", { class: "issue-type-badge" }, issue.type === 'bug' ? (h("feedlog-badge", { variant: "destructive" }, "Bug")) : (h("feedlog-badge", { variant: "enhancement" }, "Enhancement"))), issue.pinnedAt && (h("div", { class: "pinned-indicator", title: "Pinned issue" }, "\uD83D\uDCCC"))), h("div", { class: "issue-main" }, h("div", { class: "issue-details" }, h("h3", { class: "issue-title" }, issue.title), h("p", { class: "issue-body" }, issue.body), h("div", { class: "issue-repository" }, h("span", { class: "repo-name" }, issue.repository.owner, "/", issue.repository.name))), issue.type !== 'bug' && (h("button", { class: `upvote-button ${issue.hasUpvoted ? 'upvoted' : ''}`, onClick: (e) => this.handleUpvote(e), title: issue.hasUpvoted ? 'Remove upvote' : 'Upvote this issue' }, issue.hasUpvoted ? HeartFilledIcon() : HeartOutlineIcon(), h("span", { class: "upvote-count" }, issue.upvoteCount)))), h("div", { class: "issue-footer" }, h("span", { class: "issue-date", title: `Updated: ${issue.updatedAt}` }, "Updated ", this.formatDate(issue.updatedAt)), h("span", { class: "issue-date", title: `Created: ${issue.createdAt}` }, "Created ", this.formatDate(issue.createdAt)))))));
59
59
  }
60
60
  static get is() { return "feedlog-issue"; }
61
61
  static get encapsulation() { return "shadow"; }