Skip to content

feature(area): activity feed extended#887

Merged
escapedcat merged 14 commits intomainfrom
feature/area-feed-v2
Apr 11, 2026
Merged

feature(area): activity feed extended#887
escapedcat merged 14 commits intomainfrom
feature/area-feed-v2

Conversation

@escapedcat
Copy link
Copy Markdown
Contributor

@escapedcat escapedcat commented Apr 8, 2026

Based on this BE PR: teambtcmap/btcmap-api#79

Only first iteration to get things started

image

Summary by CodeRabbit

  • New Features

    • Replaced legacy activity render with a unified area activity feed that supports initial load, "Load more" pagination, skeleton placeholders, and a chevron indicator.
    • Added localized strings for activity messages and actions.
  • Reliability

    • Improved fetch handling to ignore stale responses and preserve scroll position on pagination.
  • Bug Fixes

    • Added error handling with a retry action when the activity feed fails to load.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 8, 2026

Deploy Preview for btcmap ready!

Name Link
🔨 Latest commit 4eca9ce
🔍 Latest deploy log https://app.netlify.com/projects/btcmap/deploys/69d74b43faacc500089a493f
😎 Deploy Preview https://deploy-preview-887--btcmap.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 49 (🔴 down 38 from production)
Accessibility: 97 (no change from production)
Best Practices: 92 (🔴 down 8 from production)
SEO: 96 (no change from production)
PWA: 90 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

Warning

Rate limit exceeded

@escapedcat has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 45 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 5 minutes and 45 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 169b7c91-b85a-415a-8a13-2b38666cfffd

📥 Commits

Reviewing files that changed from the base of the PR and between 2556eab and 4eca9ce.

📒 Files selected for processing (2)
  • src/components/area/AreaFeed.svelte
  • src/lib/i18n/locales/en.json
📝 Walkthrough

Walkthrough

Extracted area activity feed into a new AreaFeed component that handles fetching, pagination, race-condition guards, and UI states; AreaActivity now delegates to AreaFeed and AreaPage no longer builds ActivityEvent objects, only collects tagger metadata.

Changes

Cohort / File(s) Summary
Activity UI extraction
src/components/area/AreaActivity.svelte, src/components/area/AreaFeed.svelte
Removed eventElements prop and in-component activity rendering from AreaActivity; added AreaFeed.svelte which implements API fetching, days-based pagination, fetchGeneration race guards, loading/error/empty states, entry rendering, and scroll-preservation on load-more.
Area page logic
src/components/area/AreaPage.svelte
Removed enrichment that produced ActivityEvent entries (location/merchantId/tagger attachment). Now only deduplicates events and populates taggers by matching event.user_id. Adjusted type/value imports.
i18n
src/lib/i18n/locales/en.json
Added areaActivity keys for feed UI: loadError, retry, was, created, updated, deleted, by, boosted, commented.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant AreaFeed
    participant BTCMapAPI as "BTCMap API"
    participant UI

    User->>AreaFeed: provide alias, name, dataInitialized
    rect rgba(100,150,255,0.5)
    Note over AreaFeed: reset state (days=30, clear items, inc fetchGeneration)
    AreaFeed->>BTCMapAPI: GET /activity?alias&days=30
    BTCMapAPI-->>AreaFeed: feedItems[]
    AreaFeed->>AreaFeed: store items, set loading=false
    end

    AreaFeed->>UI: render feed entries / skeleton / error / empty

    User->>AreaFeed: click "Load more"
    rect rgba(100,150,255,0.5)
    Note over AreaFeed: increment days, inc fetchGeneration, fetch append
    AreaFeed->>BTCMapAPI: GET /activity?alias&days=60
    BTCMapAPI-->>AreaFeed: additionalItems[]
    AreaFeed->>AreaFeed: append items, restore scrollTop
    AreaFeed->>UI: re-render feed with preserved scroll
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I twitch my nose at fresh component light,

AreaFeed hums through day and night,
Fetching, paging, keeping scroll just right,
Taggers tidy, events now out of sight,
Hoppity hop—refactor delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description references the backend PR, includes a screenshot of the new UI, and notes this is the first iteration, but lacks formal sections matching the template structure and detailed explanation of changes. Consider restructuring the description to match the template with clear sections: address the related issue, provide a detailed summary of proposed changes, organize screenshots, and add any additional context.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feature(area): activity feed extended' is specific and directly relates to the main change—extending the activity feed component on area pages with new functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/area-feed-v2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

escapedcat and others added 2 commits April 9, 2026 13:08
Replace the area activity section with a new AreaFeed component that
fetches from /v4/activity?area={alias}, showing edits, comments, and
boosts in a single chronological timeline. Taggers and atom feed
sections remain unchanged.

In dev mode, requests proxy through /local-api to localhost:8000 for
testing against a local API server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove dead code: eventElements, ActivityEvent import, formatElementID
- Remove unused TopButton import from AreaFeed
- Fix key collisions by appending index to each key
- Add error state with retry button instead of silent error swallowing
- Add generation counter to prevent stale responses on area navigation
- Preserve scroll position on load-more
- Remove unused fields from ActivityItem type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@escapedcat escapedcat marked this pull request as ready for review April 9, 2026 05:22
@escapedcat escapedcat requested a review from Copilot April 9, 2026 05:22
@qodo-code-review

This comment was marked as resolved.

@qodo-code-review

This comment was marked as resolved.

This comment was marked as resolved.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/components/area/AreaFeed.svelte (3)

54-65: Scroll restoration may not work reliably with async timing.

The requestAnimationFrame schedules scroll restoration after the next paint, but Svelte's DOM updates after feedItems assignment may not complete by then. Consider using tick() from Svelte to ensure the DOM has updated before restoring scroll position.

♻️ Use Svelte's tick() for reliable DOM update timing
+import { tick } from "svelte";
+
 const loadMore = () => {
 	const scrollTop = feedDiv?.scrollTop;
 	days = days + 30;
-	fetchFeed(true).then(() => {
-		// Restore scroll position after re-render
+	fetchFeed(true).then(async () => {
+		await tick();
 		if (feedDiv && scrollTop) {
-			requestAnimationFrame(() => {
-				feedDiv.scrollTop = scrollTop;
-			});
+			feedDiv.scrollTop = scrollTop;
 		}
 	});
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/area/AreaFeed.svelte` around lines 54 - 65, The scroll
restoration in loadMore currently uses requestAnimationFrame which can race with
Svelte's DOM updates; change loadMore to await fetchFeed(true), then call
Svelte's tick() (import { tick } from 'svelte') to ensure DOM updates for
feedItems have flushed before restoring feedDiv.scrollTop; after awaiting
tick(), set feedDiv.scrollTop back (guarding feedDiv and saved scrollTop !=
null) instead of using requestAnimationFrame.

67-72: Reactive block triggers fetch on every alias or dataInitialized change.

The reactive statement will re-run whenever either dependency changes. If dataInitialized toggles multiple times or alias is reassigned to the same value, this could trigger redundant fetches. The generation guard mitigates stale responses, but consider adding a check to avoid unnecessary network calls.

♻️ Optional: Track previous alias to avoid redundant fetches
+let prevAlias: string | null = null;
+
 $: if (dataInitialized && alias) {
+	if (alias === prevAlias) return;
+	prevAlias = alias;
 	days = 30;
 	feedItems = [];
 	error = false;
 	fetchFeed(false);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/area/AreaFeed.svelte` around lines 67 - 72, The reactive block
currently re-runs and calls fetchFeed whenever dataInitialized or alias changes;
add a small guard to avoid redundant network calls by tracking the previous
alias and only invoking fetchFeed when dataInitialized becomes true and alias is
non-empty and different from the last-used alias (store lastAlias in the
component state), or when you explicitly want to reset (e.g., when days is
changed). Update the block that references dataInitialized and alias (and sets
days, feedItems, error) to compare alias against lastAlias and only call
fetchFeed(false) when alias !== lastAlias (then assign lastAlias = alias).

35-52: The append parameter doesn't actually append items.

The function signature accepts append: boolean, but feedItems = res.data always replaces the entire array. If appending was intended (for "load more" behavior), the logic should concatenate. However, since the API returns all items within the days window, replacing is likely correct—consider removing the misleading parameter name.

♻️ Suggested clarification
-const fetchFeed = async (append: boolean) => {
+const fetchFeed = async (isRetry: boolean = false) => {
 	loading = true;
 	error = false;
 	const gen = ++fetchGeneration;
 	try {
 		const base = dev ? "/local-api" : "https://api.btcmap.org";
 		const url = `${base}/v4/activity?area=${encodeURIComponent(alias)}&days=${days}`;
 		const res = await api.get<ActivityItem[]>(url);
-		// Discard stale response if user navigated to a different area
 		if (gen !== fetchGeneration) return;
 		feedItems = res.data;
 	} catch {
 		if (gen !== fetchGeneration) return;
-		if (!append) feedItems = [];
+		if (!isRetry) feedItems = [];
 		error = true;
 	}
 	loading = false;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/area/AreaFeed.svelte` around lines 35 - 52, The fetchFeed
function declares an unused append boolean and branches on it (e.g., "append"
parameter, "if (!append) feedItems = [];") but the code always replaces
feedItems with res.data; either implement true append behavior or remove the
misleading parameter. Fix by removing the append parameter from fetchFeed and
all callers, delete the append-specific conditional logic inside fetchFeed (the
"if (!append) feedItems = []" branch), and keep the single replacement
assignment feedItems = res.data; alternatively, if you prefer load-more
semantics implement concatenation (feedItems = [...feedItems, ...res.data]) and
keep append semantics consistent in callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/area/AreaFeed.svelte`:
- Around line 222-231: When rendering the error branch (the template using the
error boolean and the fetchFeed(false) retry handler), replace the misleading
no-activity text with an explicit error message key (use
$_('areaActivity.errorLoading')) and make the Retry button label use i18n
($_('areaActivity.retry')) instead of a hardcoded "Retry"; also add the new
translation keys areaActivity.errorLoading and areaActivity.retry to your i18n
files. Ensure the on:click still calls fetchFeed(false) and keep the existing
classes/structure.

---

Nitpick comments:
In `@src/components/area/AreaFeed.svelte`:
- Around line 54-65: The scroll restoration in loadMore currently uses
requestAnimationFrame which can race with Svelte's DOM updates; change loadMore
to await fetchFeed(true), then call Svelte's tick() (import { tick } from
'svelte') to ensure DOM updates for feedItems have flushed before restoring
feedDiv.scrollTop; after awaiting tick(), set feedDiv.scrollTop back (guarding
feedDiv and saved scrollTop != null) instead of using requestAnimationFrame.
- Around line 67-72: The reactive block currently re-runs and calls fetchFeed
whenever dataInitialized or alias changes; add a small guard to avoid redundant
network calls by tracking the previous alias and only invoking fetchFeed when
dataInitialized becomes true and alias is non-empty and different from the
last-used alias (store lastAlias in the component state), or when you explicitly
want to reset (e.g., when days is changed). Update the block that references
dataInitialized and alias (and sets days, feedItems, error) to compare alias
against lastAlias and only call fetchFeed(false) when alias !== lastAlias (then
assign lastAlias = alias).
- Around line 35-52: The fetchFeed function declares an unused append boolean
and branches on it (e.g., "append" parameter, "if (!append) feedItems = [];")
but the code always replaces feedItems with res.data; either implement true
append behavior or remove the misleading parameter. Fix by removing the append
parameter from fetchFeed and all callers, delete the append-specific conditional
logic inside fetchFeed (the "if (!append) feedItems = []" branch), and keep the
single replacement assignment feedItems = res.data; alternatively, if you prefer
load-more semantics implement concatenation (feedItems = [...feedItems,
...res.data]) and keep append semantics consistent in callers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2f9a5fc-d89f-4259-8db1-b9acd08ece6f

📥 Commits

Reviewing files that changed from the base of the PR and between 59c00dd and 5d72299.

📒 Files selected for processing (4)
  • src/components/area/AreaActivity.svelte
  • src/components/area/AreaFeed.svelte
  • src/components/area/AreaPage.svelte
  • vite.config.ts

escapedcat and others added 5 commits April 9, 2026 13:36
Both osm_user_id and osm_user_name must be present before rendering
the tagger link, since both fields are optional in the API response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If the fetch fails, restore the previous days value so the next
retry attempts the same range instead of skipping ahead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When navigating between areas, the scroll chevron indicator stayed
hidden. Reset it so the new feed shows the chevron again.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add areaActivity.loadError and areaActivity.retry i18n keys.
Error state now shows distinct message instead of reusing noActivity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split mixed import statement per CLAUDE.md guidelines — type-only
imports use `import type`, runtime values use separate `import`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@escapedcat escapedcat force-pushed the feature/area-feed-v2 branch from 5d72299 to 21043c3 Compare April 9, 2026 05:41
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/components/area/AreaFeed.svelte (2)

145-151: Use i18n for activity action labels.

The strings "created", "deleted", "updated" are hardcoded in English. For consistency with the rest of the application, these should use translation keys.

♻️ Suggested refactor
 was <strong
-	>{item.type === 'place_added'
-		? 'created'
-		: item.type === 'place_deleted'
-			? 'deleted'
-			: 'updated'}</strong
+	>{item.type === 'place_added'
+		? $_('areaActivity.created')
+		: item.type === 'place_deleted'
+			? $_('areaActivity.deleted')
+			: $_('areaActivity.updated')}</strong
 >

Add the corresponding keys to your i18n files:

"areaActivity": {
  "created": "created",
  "deleted": "deleted",
  "updated": "updated"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/area/AreaFeed.svelte` around lines 145 - 151, Replace the
hardcoded English action labels in AreaFeed.svelte (the ternary using item.type)
with i18n lookups so the UI uses translation keys (e.g., areaActivity.created /
areaActivity.deleted / areaActivity.updated) instead of the literal strings;
update your i18n resource files to include those keys and ensure the component
imports/uses the translation function (or $t store) the app uses so the ternary
returns translated labels for item.type === 'place_added' | 'place_deleted' |
otherwise.

193-193: Use i18n with pluralization for boost duration.

The text "was boosted for {duration} days" is hardcoded. Consider using an i18n key with ICU pluralization to handle "1 day" vs "N days" correctly across languages.

♻️ Suggested refactor
-was <strong>boosted</strong> for {item.duration_days} days
+{$_('areaActivity.boosted', { values: { days: item.duration_days } })}

With i18n key:

"boosted": "was <strong>boosted</strong> for {days, plural, one {# day} other {# days}}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/area/AreaFeed.svelte` at line 193, The hardcoded fragment "was
<strong>boosted</strong> for {item.duration_days} days" should be replaced with
an i18n key using ICU pluralization so "1 day" vs "N days" is correct and the
<strong> markup is preserved; add a translation key like "boosted": "was
<strong>boosted</strong> for {days, plural, one {# day} other {# days}}" and
update the AreaFeed.svelte usage to call your i18n translate function (e.g., t
or $t) passing days: item.duration_days and render the translated string as
HTML-safe content so the <strong> stays bold. Ensure you reference the string in
AreaFeed.svelte where item.duration_days is used and remove the hardcoded
sentence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/area/AreaFeed.svelte`:
- Around line 145-151: Replace the hardcoded English action labels in
AreaFeed.svelte (the ternary using item.type) with i18n lookups so the UI uses
translation keys (e.g., areaActivity.created / areaActivity.deleted /
areaActivity.updated) instead of the literal strings; update your i18n resource
files to include those keys and ensure the component imports/uses the
translation function (or $t store) the app uses so the ternary returns
translated labels for item.type === 'place_added' | 'place_deleted' | otherwise.
- Line 193: The hardcoded fragment "was <strong>boosted</strong> for
{item.duration_days} days" should be replaced with an i18n key using ICU
pluralization so "1 day" vs "N days" is correct and the <strong> markup is
preserved; add a translation key like "boosted": "was <strong>boosted</strong>
for {days, plural, one {# day} other {# days}}" and update the AreaFeed.svelte
usage to call your i18n translate function (e.g., t or $t) passing days:
item.duration_days and render the translated string as HTML-safe content so the
<strong> stays bold. Ensure you reference the string in AreaFeed.svelte where
item.duration_days is used and remove the hardcoded sentence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e4317c7-69ad-49c9-8c9d-5c8cbf920c81

📥 Commits

Reviewing files that changed from the base of the PR and between 5d72299 and 21043c3.

📒 Files selected for processing (5)
  • src/components/area/AreaActivity.svelte
  • src/components/area/AreaFeed.svelte
  • src/components/area/AreaPage.svelte
  • src/lib/i18n/locales/en.json
  • vite.config.ts
✅ Files skipped from review due to trivial changes (2)
  • src/lib/i18n/locales/en.json
  • vite.config.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/area/AreaActivity.svelte

escapedcat and others added 2 commits April 9, 2026 13:51
Replace magic number 30 with named constant for clarity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the /local-api proxy from vite.config.ts and the dev/prod
URL toggle from AreaFeed. A proper local dev solution can be
designed later across the whole app.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/components/area/AreaFeed.svelte (3)

145-159: Hardcoded English strings should use i18n for consistency.

The component uses $_() for translations elsewhere but has hardcoded strings here: "created", "deleted", "updated", and "by". For i18n consistency, consider adding translation keys.

♻️ Proposed i18n approach
 was <strong
 	>{item.type === 'place_added'
-		? 'created'
+		? $_('areaActivity.created')
 		: item.type === 'place_deleted'
-			? 'deleted'
-			: 'updated'}</strong
+			? $_('areaActivity.deleted')
+			: $_('areaActivity.updated')}</strong
 >
 {`#if` item.osm_user_id && item.osm_user_name}
-	by <a
+	{$_('areaActivity.by')} <a
 		href={resolve(`/tagger/${item.osm_user_id}`)}
 		class="break-all text-link transition-colors hover:text-hover"
 	>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/area/AreaFeed.svelte` around lines 145 - 159, The template in
AreaFeed.svelte is using hardcoded English strings for the place status and the
"by" prefix (the ternary block checking item.type and the literal "by"); replace
those literals with i18n lookups (use the existing $_() helper used elsewhere)
e.g. map item.type to translation keys like $_('place.created'),
$_('place.deleted'), $_('place.updated') and replace "by" with $_('by') (or
project-appropriate keys), update your i18n resource files with those keys, and
ensure the $_ helper is available in the component scope before rendering.

193-193: Hardcoded boost message should use i18n with interpolation.

This string should also use a translation key with a placeholder for the duration.

♻️ Proposed fix
-was <strong>boosted</strong> for {item.duration_days} days
+{$_('areaActivity.boosted', { values: { days: item.duration_days } })}

Translation key example: "boosted": "was <strong>boosted</strong> for {days} days"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/area/AreaFeed.svelte` at line 193, Replace the hardcoded HTML
string in AreaFeed.svelte ("was <strong>boosted</strong> for
{item.duration_days} days") with an i18n translation key that includes an
interpolation placeholder for days (e.g., add a translation entry "boosted":
"was <strong>boosted</strong> for {days} days") and render it using the
project's translation helper, passing item.duration_days as the days parameter
so the UI uses localized text with the correct duration.

104-130: Consider simplifying dot rendering with a color map.

The five conditional blocks are nearly identical, differing only in the background color. A mapping would reduce duplication.

♻️ Optional simplification

Add a helper in the script section:

const dotColor: Record<string, string> = {
	place_commented: 'bg-amber-500',
	place_boosted: 'bg-orange-500',
	place_added: 'bg-created',
	place_deleted: 'bg-deleted'
};

Then in the template:

{`@const` color = dotColor[item.type] ?? 'bg-link'}
<span class="relative mx-auto mb-2 flex h-3 w-3 lg:mx-0 lg:mb-0">
	<span class="{i === 0 ? 'animate-ping' : ''} absolute inline-flex h-full w-full rounded-full {color} opacity-75" />
	<span class="relative inline-flex h-3 w-3 rounded-full {color}" />
</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/area/AreaFeed.svelte` around lines 104 - 130, The repeated
conditional dot-rendering based on item.type should be replaced by a color map
to remove duplication: add a helper (e.g., const dotColor:
Record<string,string>) mapping
'place_commented','place_boosted','place_added','place_deleted' to their
respective Tailwind classes and fall back to 'bg-link', then in the template
compute const color = dotColor[item.type] ?? 'bg-link' and render a single
wrapper <span> containing two spans using {i === 0 ? 'animate-ping' : ''} for
the absolute pulsing layer and the solid dot layer both using the computed
{color} classes instead of the multiple {:if} branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/area/AreaFeed.svelte`:
- Around line 145-159: The template in AreaFeed.svelte is using hardcoded
English strings for the place status and the "by" prefix (the ternary block
checking item.type and the literal "by"); replace those literals with i18n
lookups (use the existing $_() helper used elsewhere) e.g. map item.type to
translation keys like $_('place.created'), $_('place.deleted'),
$_('place.updated') and replace "by" with $_('by') (or project-appropriate
keys), update your i18n resource files with those keys, and ensure the $_ helper
is available in the component scope before rendering.
- Line 193: Replace the hardcoded HTML string in AreaFeed.svelte ("was
<strong>boosted</strong> for {item.duration_days} days") with an i18n
translation key that includes an interpolation placeholder for days (e.g., add a
translation entry "boosted": "was <strong>boosted</strong> for {days} days") and
render it using the project's translation helper, passing item.duration_days as
the days parameter so the UI uses localized text with the correct duration.
- Around line 104-130: The repeated conditional dot-rendering based on item.type
should be replaced by a color map to remove duplication: add a helper (e.g.,
const dotColor: Record<string,string>) mapping
'place_commented','place_boosted','place_added','place_deleted' to their
respective Tailwind classes and fall back to 'bg-link', then in the template
compute const color = dotColor[item.type] ?? 'bg-link' and render a single
wrapper <span> containing two spans using {i === 0 ? 'animate-ping' : ''} for
the absolute pulsing layer and the solid dot layer both using the computed
{color} classes instead of the multiple {:if} branches.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cee2a433-a919-4690-8527-f56bf867d3d0

📥 Commits

Reviewing files that changed from the base of the PR and between 21043c3 and f040e20.

📒 Files selected for processing (1)
  • src/components/area/AreaFeed.svelte

escapedcat and others added 2 commits April 9, 2026 14:16
Add areaActivity.was, created, updated, deleted, by, boosted, and
commented i18n keys. The boosted string uses interpolation for the
duration days value and @html for the <strong> tag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 5 conditional branches with a DOT_COLORS lookup map and a
dotColor() helper. Adding new activity types now only requires a
map entry instead of duplicating the dot markup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/area/AreaFeed.svelte (1)

46-62: Consider clarifying the append parameter semantics.

The append parameter only affects error handling (line 58) and doesn't actually append on success—line 55 always replaces feedItems. This is likely intentional since the API returns cumulative data for the days window, but the parameter name is misleading.

✏️ Suggested rename for clarity
-const fetchFeed = async (append: boolean) => {
+const fetchFeed = async (preserveOnError: boolean) => {
 	loading = true;
 	error = false;
 	const gen = ++fetchGeneration;
 	try {
 		const url = `https://api.btcmap.org/v4/activity?area=${encodeURIComponent(alias)}&days=${days}`;
 		const res = await api.get<ActivityItem[]>(url);
 		// Discard stale response if user navigated to a different area
 		if (gen !== fetchGeneration) return;
 		feedItems = res.data;
 	} catch {
 		if (gen !== fetchGeneration) return;
-		if (!append) feedItems = [];
+		if (!preserveOnError) feedItems = [];
 		error = true;
 	}
 	loading = false;
 };

Then update callsites: fetchFeed(false)fetchFeed(false) (unchanged), fetchFeed(true)fetchFeed(true) (unchanged, but now the meaning is clearer).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/area/AreaFeed.svelte` around lines 46 - 62, The parameter
append on fetchFeed only controls whether feedItems is cleared on error and
doesn't append on success, so rename the parameter to something that reflects
that behavior (e.g., preserveOnError or suppressClearOnError) in the fetchFeed
function signature and its usages; update any inline comment/docstring for
fetchFeed to describe "preserve existing feed on error" semantics, replace all
callsites passing true/false to use the new parameter name, and ensure
references to feedItems and the error branch logic (the if (!append) feedItems =
[] block) are updated to use the new identifier.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/area/AreaFeed.svelte`:
- Around line 108-201: The loadMore failure currently sets error=true but
because the template first checks feedItems.length the error UI never appears;
update the loadMore handler to track a dedicated loadMoreError state (e.g.,
loadMoreError or loadMoreFailed) and set it when the fetch rejects, then update
the template around the Load more button (the button with on:click={loadMore})
to show inline feedback — disable the button when loading or loadMoreError, add
error styling/text or a small toast/inline message adjacent to that button, and
ensure feed rendering (feedItems) remains unchanged while the user clearly sees
the loadMore failure.

---

Nitpick comments:
In `@src/components/area/AreaFeed.svelte`:
- Around line 46-62: The parameter append on fetchFeed only controls whether
feedItems is cleared on error and doesn't append on success, so rename the
parameter to something that reflects that behavior (e.g., preserveOnError or
suppressClearOnError) in the fetchFeed function signature and its usages; update
any inline comment/docstring for fetchFeed to describe "preserve existing feed
on error" semantics, replace all callsites passing true/false to use the new
parameter name, and ensure references to feedItems and the error branch logic
(the if (!append) feedItems = [] block) are updated to use the new identifier.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fea82ab3-7266-4103-86d8-18211fe14364

📥 Commits

Reviewing files that changed from the base of the PR and between f040e20 and 2556eab.

📒 Files selected for processing (2)
  • src/components/area/AreaFeed.svelte
  • src/lib/i18n/locales/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/i18n/locales/en.json

When load-more fails with existing items, the error state was
unreachable because the feedItems.length branch always wins. Now
an inline error with retry button renders above the load-more
button when a pagination fetch fails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
escapedcat and others added 2 commits April 9, 2026 14:40
Translations should not contain HTML — keeps translation files plain
text and avoids @html in the template. The boosted phrase is now
composed from areaActivity.was, areaActivity.boosted (plain), and
areaActivity.forDays with days interpolation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The append param was misleading — fetchFeed always replaces feedItems
with fresh API data, never concatenates. The flag only controlled
whether to clear items on error. Replaced with an internal hadItems
check based on current state, simpler and clearer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@escapedcat escapedcat merged commit 6908dfa into main Apr 11, 2026
12 checks passed
@escapedcat escapedcat deleted the feature/area-feed-v2 branch April 11, 2026 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants